Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> to variables
* (like Class.staticMethod).
*
* When building scope information, also declares relevant information
* about types in the type registry.
*
* @author nicksantos@google.com (Nick Santos)
*/
final class TypedScopeCreator implements ScopeCreator {
/**
* A suffix for naming delegate proxies differently from their base.
*/
static final String DELEGATE_PROXY_SUFFIX =
ObjectType.createDelegateSuffix("Proxy");
static final DiagnosticType MALFORMED_TYPEDEF =
DiagnosticType.warning(
"JSC_MALFORMED_TYPEDEF",
"Typedef for {0} does not have any type information");
static final DiagnosticType ENUM_INITIALIZER =
DiagnosticType.warning(
"JSC_ENUM_INITIALIZER_NOT_ENUM",
"enum initializer must be an object literal or an enum");
static final DiagnosticType CTOR_INITIALIZER =
DiagnosticType.warning(
"JSC_CTOR_INITIALIZER_NOT_CTOR",
"Constructor {0} must be initialized at declaration");
static final DiagnosticType IFACE_INITIALIZER =
DiagnosticType.warning(
"JSC_IFACE_INITIALIZER_NOT_IFACE",
"Interface {0} must be initialized at declaration");
static final DiagnosticType CONSTRUCTOR_EXPECTED =
DiagnosticType.warning(
"JSC_REFLECT_CONSTRUCTOR_EXPECTED",
"Constructor expected as first argument");
static final DiagnosticType UNKNOWN_LENDS =
DiagnosticType.warning(
"JSC_UNKNOWN_LENDS",
"Variable {0} not declared before @lends annotation.");
static final DiagnosticType LENDS_ON_NON_OBJECT =
DiagnosticType.warning(
"JSC_LENDS_ON_NON_OBJECT",
"May only lend properties to object types. {0} has type {1}.");
private final AbstractCompiler compiler;
private final ErrorReporter typeParsingErrorReporter;
private final TypeValidator validator;
private final CodingConvention codingConvention;
private final JSTypeRegistry typeRegistry;
private final List<ObjectType> delegateProxyPrototypes = Lists.newArrayList();
private final Map<String, String> delegateCallingConventions =
Maps.newHashMap();
// Simple properties inferred about functions.
private final Map<Node, AstFunctionContents> functionAnalysisResults =
Maps.newHashMap();
// For convenience
private final ObjectType unknownType;
/**
* Defer attachment of types to nodes until all type names
* have been resolved. Then, we can resolve the type and attach it.
*/
private class DeferredSetType {
final Node node;
final JSType type;
DeferredSetType(Node node, JSType type) {
Preconditions.checkNotNull(node);
Preconditions.checkNotNull(type);
this.node = node;
this.type = type;
// Other parts of this pass may read off the node.
// (like when we set the LHS of an assign with a typed RHS function.)
node.setJSType(type);
}
void resolve(Scope scope) {
node.setJSType(type.resolve(typeParsingErrorReporter, scope));
}
}
TypedScopeCreator(AbstractCompiler compiler) {
this(compiler, compiler.getCodingConvention());
}
Typed
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> SCRIPT nodes
// and a global typed scope should have been generated already.
Preconditions.checkState(scriptRoot.isScript());
Preconditions.checkNotNull(globalScope);
Preconditions.checkState(globalScope.isGlobal());
String scriptName = NodeUtil.getSourceName(scriptRoot);
Preconditions.checkNotNull(scriptName);
for (Node node : ImmutableList.copyOf(functionAnalysisResults.keySet())) {
if (scriptName.equals(NodeUtil.getSourceName(node))) {
functionAnalysisResults.remove(node);
}
}
(new FirstOrderFunctionAnalyzer(
compiler, functionAnalysisResults)).process(null, scriptRoot);
// TODO(bashir): Variable declaration is not the only side effect of last
// global scope generation but here we only wipe that part off!
// Remove all variables that were previously declared in this scripts.
// First find all vars to remove then remove them because of iterator!
Iterator<Var> varIter = globalScope.getVars();
List<Var> varsToRemove = Lists.newArrayList();
while (varIter.hasNext()) {
Var oldVar = varIter.next();
if (scriptName.equals(oldVar.getInputName())) {
varsToRemove.add(oldVar);
}
}
for (Var var : varsToRemove) {
globalScope.undeclare(var);
globalScope.getTypeOfThis().toObjectType().removeProperty(var.getName());
}
// Now re-traverse the given script.
GlobalScopeBuilder scopeBuilder = new GlobalScopeBuilder(globalScope);
NodeTraversal.traverse(compiler, scriptRoot, scopeBuilder);
}
/**
* Create the outermost scope. This scope contains native binding such as
* {@code Object}, {@code Date}, etc.
*/
@VisibleForTesting
Scope createInitialScope(Node root) {
NodeTraversal.traverse(
compiler, root, new DiscoverEnumsAndTypedefs(typeRegistry));
Scope s = Scope.createGlobalScope(root);
declareNativeFunctionType(s, ARRAY_FUNCTION_TYPE);
declareNativeFunctionType(s, BOOLEAN_OBJECT_FUNCTION_TYPE);
declareNativeFunctionType(s, DATE_FUNCTION_TYPE);
declareNativeFunctionType(s, ERROR_FUNCTION_TYPE);
declareNativeFunctionType(s, EVAL_ERROR_FUNCTION_TYPE);
declareNativeFunctionType(s, FUNCTION_FUNCTION_TYPE);
declareNativeFunctionType(s, NUMBER_OBJECT_FUNCTION_TYPE);
declareNativeFunctionType(s, OBJECT_FUNCTION_TYPE);
declareNativeFunctionType(s, RANGE_ERROR_FUNCTION_TYPE);
declareNativeFunctionType(s, REFERENCE_ERROR_FUNCTION_TYPE);
declareNativeFunctionType(s, REGEXP_FUNCTION_TYPE);
declareNativeFunctionType(s, STRING_OBJECT_FUNCTION_TYPE);
declareNativeFunctionType(s, SYNTAX_ERROR_FUNCTION_TYPE);
declareNativeFunctionType(s, TYPE_ERROR_FUNCTION_TYPE);
declareNativeFunctionType(s, URI_ERROR_FUNCTION_TYPE);
declareNativeValueType(s, "undefined", VOID_TYPE);
// There is no longer a need to special case ActiveXObject
// but this remains here until we can get the extern forks
// cleaned up.
declareNativeValueType(s, "ActiveXObject", FUNCTION_INSTANCE_TYPE);
return s;
}
private
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> void declareNativeFunctionType(Scope scope, JSTypeNative tId) {
FunctionType t = typeRegistry.getNativeFunctionType(tId);
declareNativeType(scope, t.getInstanceType().getReferenceName(), t);
declareNativeType(
scope, t.getPrototype().getReferenceName(), t.getPrototype());
}
private void declareNativeValueType(Scope scope, String name,
JSTypeNative tId) {
declareNativeType(scope, name, typeRegistry.getNativeType(tId));
}
private static void declareNativeType(Scope scope, String name, JSType t) {
scope.declare(name, null, t, null, false);
}
private static class DiscoverEnumsAndTypedefs
extends AbstractShallowStatementCallback {
private final JSTypeRegistry registry;
DiscoverEnumsAndTypedefs(JSTypeRegistry registry) {
this.registry = registry;
}
@Override
public void visit(NodeTraversal t, Node node, Node parent) {
switch (node.getType()) {
case Token.VAR:
for (Node child = node.getFirstChild();
child != null; child = child.getNext()) {
identifyNameNode(
child, NodeUtil.getBestJSDocInfo(child));
}
break;
case Token.EXPR_RESULT:
Node firstChild = node.getFirstChild();
if (firstChild.isAssign()) {
identifyNameNode(
firstChild.getFirstChild(), firstChild.getJSDocInfo());
} else {
identifyNameNode(
firstChild, firstChild.getJSDocInfo());
}
break;
}
}
private void identifyNameNode(
Node nameNode, JSDocInfo info) {
if (nameNode.isQualifiedName()) {
if (info != null) {
if (info.hasEnumParameterType()) {
registry.identifyNonNullableName(nameNode.getQualifiedName());
} else if (info.hasTypedefType()) {
registry.identifyNonNullableName(nameNode.getQualifiedName());
}
}
}
}
}
private JSType getNativeType(JSTypeNative nativeType) {
return typeRegistry.getNativeType(nativeType);
}
private abstract class AbstractScopeBuilder
implements NodeTraversal.Callback, Scope.TypeResolver {
/**
* The scope that we're building.
*/
final Scope scope;
private final List<DeferredSetType> deferredSetTypes =
Lists.newArrayList();
/**
* Functions that we found in the global scope and not in externs.
*/
private final List<Node> nonExternFunctions = Lists.newArrayList();
/**
* Object literals with a @lends annotation aren't analyzed until we
* reach the root of the statement they're defined in.
*
* This ensures that if there are any @lends annotations on the object
* literals, the type on the @lends annotation resolves correctly.
*
* For more information, see
* http://code.google.com/p/closure-compiler/issues/detail?id=314
*/
private List<Node> lentObjectLiterals = null;
/**
* Type-less stubs.
*
* If at the end of traversal, we still don't have types for these
* stubs, then
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> we should declare UNKNOWN types.
*/
private final List<StubDeclaration> stubDeclarations =
Lists.newArrayList();
/**
* The current source file that we're in.
*/
private String sourceName = null;
/**
* The InputId of the current node.
*/
private InputId inputId;
private AbstractScopeBuilder(Scope scope) {
this.scope = scope;
}
void setDeferredType(Node node, JSType type) {
deferredSetTypes.add(new DeferredSetType(node, type));
}
@Override
public void resolveTypes() {
// Resolve types and attach them to nodes.
for (DeferredSetType deferred : deferredSetTypes) {
deferred.resolve(scope);
}
// Resolve types and attach them to scope slots.
Iterator<Var> vars = scope.getVars();
while (vars.hasNext()) {
vars.next().resolveType(typeParsingErrorReporter);
}
// Tell the type registry that any remaining types
// are unknown.
typeRegistry.resolveTypesInScope(scope);
}
@Override
public final boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
inputId = t.getInputId();
if (n.isFunction() ||
n.isScript()) {
Preconditions.checkNotNull(inputId);
sourceName = NodeUtil.getSourceName(n);
}
// We do want to traverse the name of a named function, but we don't
// want to traverse the arguments or body.
boolean descend = parent == null || !parent.isFunction() ||
n == parent.getFirstChild() || parent == scope.getRootNode();
if (descend) {
// Handle hoisted functions on pre-order traversal, so that they
// get hit before other things in the scope.
if (NodeUtil.isStatementParent(n)) {
for (Node child = n.getFirstChild();
child != null;
child = child.getNext()) {
if (NodeUtil.isHoistedFunctionDeclaration(child)) {
defineFunctionLiteral(child);
}
}
}
}
return descend;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
inputId = t.getInputId();
attachLiteralTypes(n);
switch (n.getType()) {
case Token.CALL:
checkForClassDefiningCalls(t, n);
checkForCallingConventionDefiningCalls(n, delegateCallingConventions);
break;
case Token.FUNCTION:
if (t.getInput() == null || !t.getInput().isExtern()) {
nonExternFunctions.add(n);
}
// Hoisted functions are handled during pre-traversal.
if (!NodeUtil.isHoistedFunctionDeclaration(n)) {
defineFunctionLiteral(n);
}
break;
case Token.ASSIGN:
// Handle initialization of properties.
Node firstChild = n.getFirstChild();
if (firstChild.isGetProp() &&
firstChild.isQualifiedName()) {
maybeDeclareQualifiedName(t, n.getJSDocInfo(),
firstChild, n, firstChild.getNext());
}
break;
case Token.CATCH:
defineCatch(n);
break;
case Token.VAR:
defineVar(
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>n);
break;
case Token.GETPROP:
// Handle stubbed properties.
if (parent.isExprResult() &&
n.isQualifiedName()) {
maybeDeclareQualifiedName(t, n.getJSDocInfo(), n, parent, null);
}
break;
}
// Analyze any @lends object literals in this statement.
if (n.getParent() != null && NodeUtil.isStatement(n) &&
lentObjectLiterals != null) {
for (Node objLit : lentObjectLiterals) {
defineObjectLiteral(objLit);
}
lentObjectLiterals.clear();
}
}
private void attachLiteralTypes(Node n) {
switch (n.getType()) {
case Token.NULL:
n.setJSType(getNativeType(NULL_TYPE));
break;
case Token.VOID:
n.setJSType(getNativeType(VOID_TYPE));
break;
case Token.STRING:
n.setJSType(getNativeType(STRING_TYPE));
break;
case Token.NUMBER:
n.setJSType(getNativeType(NUMBER_TYPE));
break;
case Token.TRUE:
case Token.FALSE:
n.setJSType(getNativeType(BOOLEAN_TYPE));
break;
case Token.REGEXP:
n.setJSType(getNativeType(REGEXP_TYPE));
break;
case Token.OBJECTLIT:
JSDocInfo info = n.getJSDocInfo();
if (info != null &&
info.getLendsName() != null) {
if (lentObjectLiterals == null) {
lentObjectLiterals = Lists.newArrayList();
}
lentObjectLiterals.add(n);
} else {
defineObjectLiteral(n);
}
break;
// NOTE(nicksantos): If we ever support Array tuples,
// we will need to put ARRAYLIT here as well.
}
}
private void defineObjectLiteral(Node objectLit) {
// Handle the @lends annotation.
JSType type = null;
JSDocInfo info = objectLit.getJSDocInfo();
if (info != null && info.getLendsName() != null) {
String lendsName = info.getLendsName();
Var lendsVar = scope.getVar(lendsName);
if (lendsVar == null) {
compiler.report(
JSError.make(sourceName, objectLit, UNKNOWN_LENDS, lendsName));
} else {
type = lendsVar.getType();
if (type == null) {
type = unknownType;
}
if (!type.isSubtype(typeRegistry.getNativeType(OBJECT_TYPE))) {
compiler.report(
JSError.make(sourceName, objectLit, LENDS_ON_NON_OBJECT,
lendsName, type.toString()));
type = null;
} else {
objectLit.setJSType(type);
}
}
}
info = NodeUtil.getBestJSDocInfo(objectLit);
Node lValue = NodeUtil.getBestLValue(objectLit);
String lValueName = NodeUtil.getBestLValueName
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>(lValue);
boolean createdEnumType = false;
if (info != null && info.hasEnumParameterType()) {
type = createEnumTypeFromNodes(objectLit, lValueName, info, lValue);
createdEnumType = true;
}
if (type == null) {
type = typeRegistry.createAnonymousObjectType(info);
}
setDeferredType(objectLit, type);
// If this is an enum, the properties were already taken care of above.
processObjectLitProperties(
objectLit, ObjectType.cast(objectLit.getJSType()), !createdEnumType);
}
/**
* Process an object literal and all the types on it.
* @param objLit The OBJECTLIT node.
* @param objLitType The type of the OBJECTLIT node. This might be a named
* type, because of the lends annotation.
* @param declareOnOwner If true, declare properties on the objLitType as
* well. If false, the caller should take care of this.
*/
void processObjectLitProperties(
Node objLit, ObjectType objLitType,
boolean declareOnOwner) {
for (Node keyNode = objLit.getFirstChild(); keyNode != null;
keyNode = keyNode.getNext()) {
Node value = keyNode.getFirstChild();
String memberName = NodeUtil.getObjectLitKeyName(keyNode);
JSDocInfo info = keyNode.getJSDocInfo();
JSType valueType = getDeclaredType(info, keyNode, value);
JSType keyType = objLitType.isEnumType() ?
objLitType.toMaybeEnumType().getElementsType() :
NodeUtil.getObjectLitKeyTypeFromValueType(keyNode, valueType);
// Try to declare this property in the current scope if it
// has an authoritative name.
String qualifiedName = NodeUtil.getBestLValueName(keyNode);
if (qualifiedName != null) {
boolean inferred = keyType == null;
defineSlot(keyNode, objLit, qualifiedName, keyType, inferred);
} else if (keyType != null) {
setDeferredType(keyNode, keyType);
}
if (keyType != null && objLitType != null && declareOnOwner) {
// Declare this property on its object literal.
objLitType.defineDeclaredProperty(memberName, keyType, keyNode);
}
}
}
/**
* Returns the type specified in a JSDoc annotation near a GETPROP or NAME.
*
* Extracts type information from either the {@code @type} tag or from
* the {@code @return} and {@code @param} tags.
*/
private JSType getDeclaredTypeInAnnotation(Node node, JSDocInfo info) {
JSType jsType = null;
if (info != null) {
if (info.hasType()) {
ImmutableList<TemplateType> ownerTypeKeys = ImmutableList.of();
Node ownerNode = NodeUtil.getBestLValueOwner(node);
String ownerName = NodeUtil.getBestLValueName(ownerNode);
ObjectType ownerType = null;
if (ownerName != null) {
Var ownerVar = scope.getVar(ownerName);
if (ownerVar != null) {
owner
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>Type = getPrototypeOwnerType(
ObjectType.cast(ownerVar.getType()));
if (ownerType != null) {
ownerTypeKeys =
ownerType.getTemplateTypeMap().getTemplateKeys();
}
}
}
if (!ownerTypeKeys.isEmpty()) {
typeRegistry.setTemplateTypeNames(ownerTypeKeys);
}
jsType = info.getType().evaluate(scope, typeRegistry);
if (!ownerTypeKeys.isEmpty()) {
typeRegistry.clearTemplateTypeNames();
}
} else if (FunctionTypeBuilder.isFunctionTypeDeclaration(info)) {
String fnName = node.getQualifiedName();
jsType = createFunctionTypeFromNodes(
null, fnName, info, node);
}
}
return jsType;
}
/**
* Asserts that it's OK to define this node's name.
* The node should have a source name and be of the specified type.
*/
void assertDefinitionNode(Node n, int type) {
Preconditions.checkState(sourceName != null);
Preconditions.checkState(n.getType() == type);
}
/**
* Defines a catch parameter.
*/
void defineCatch(Node n) {
assertDefinitionNode(n, Token.CATCH);
Node catchName = n.getFirstChild();
defineSlot(catchName, n,
getDeclaredType(
catchName.getJSDocInfo(), catchName, null));
}
/**
* Defines a VAR initialization.
*/
void defineVar(Node n) {
assertDefinitionNode(n, Token.VAR);
JSDocInfo info = n.getJSDocInfo();
if (n.hasMoreThanOneChild()) {
if (info != null) {
// multiple children
compiler.report(JSError.make(sourceName, n, MULTIPLE_VAR_DEF));
}
for (Node name : n.children()) {
defineName(name, n, name.getJSDocInfo());
}
} else {
Node name = n.getFirstChild();
defineName(name, n, (info != null) ? info : name.getJSDocInfo());
}
}
/**
* Defines a function literal.
*/
void defineFunctionLiteral(Node n) {
assertDefinitionNode(n, Token.FUNCTION);
// Determine the name and JSDocInfo and l-value for the function.
// Any of these may be null.
Node lValue = NodeUtil.getBestLValue(n);
JSDocInfo info = NodeUtil.getBestJSDocInfo(n);
String functionName = NodeUtil.getBestLValueName(lValue);
FunctionType functionType =
createFunctionTypeFromNodes(n, functionName, info, lValue);
// Assigning the function type to the function node
setDeferredType(n, functionType);
// Declare this symbol in the current scope iff it's a function
// declaration. Otherwise, the declaration will happen in other
// code paths.
if (NodeUtil.isFunctionDeclaration(n)) {
defineSlot(n.getFirstChild(), n, functionType);
}
}
/**
* Defines a variable based on the {@link Token#NAME} node passed.
* @param name The {@link Token#NAME} node.
*
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> @param var The parent of the {@code name} node, which must be a
* {@link Token#VAR} node.
* @param info the {@link JSDocInfo} information relating to this
* {@code name} node.
*/
private void defineName(Node name, Node var, JSDocInfo info) {
Node value = name.getFirstChild();
// variable's type
JSType type = getDeclaredType(info, name, value);
if (type == null) {
// The variable's type will be inferred.
type = name.isFromExterns() ? unknownType : null;
}
defineSlot(name, var, type);
}
/**
* If a variable is assigned a function literal in the global scope,
* make that a declared type (even if there's no doc info).
* There's only one exception to this rule:
* if the return type is inferred, and we're in a local
* scope, we should assume the whole function is inferred.
*/
private boolean shouldUseFunctionLiteralType(
FunctionType type, JSDocInfo info, Node lValue) {
if (info != null) {
return true;
}
if (lValue != null &&
NodeUtil.isObjectLitKey(lValue)) {
return false;
}
return scope.isGlobal() || !type.isReturnTypeInferred();
}
/**
* Creates a new function type, based on the given nodes.
*
* This handles two cases that are semantically very different, but
* are not mutually exclusive:
* - A function literal that needs a type attached to it.
* - An assignment expression with function-type info in the JsDoc.
*
* All parameters are optional, and we will do the best we can to create
* a function type.
*
* This function will always create a function type, so only call it if
* you're sure that's what you want.
*
* @param rValue The function node.
* @param name the function's name
* @param info the {@link JSDocInfo} attached to the function definition
* @param lvalueNode The node where this function is being
* assigned. For example, {@code A.prototype.foo = ...} would be used to
* determine that this function is a method of A.prototype. May be
* null to indicate that this is not being assigned to a qualified name.
*/
private FunctionType createFunctionTypeFromNodes(
@Nullable Node rValue,
@Nullable String name,
@Nullable JSDocInfo info,
@Nullable Node lvalueNode) {
FunctionType functionType = null;
// Global ctor aliases should be registered with the type registry.
if (rValue != null && rValue.isQualifiedName() && scope.isGlobal()) {
Var var = scope.getVar(rValue.getQualifiedName());
if (var != null && var.getType() != null &&
var.getType().isFunctionType()) {
FunctionType aliasedType = var.getType().toMaybeFunctionType();
if ((aliasedType.isConstructor() || aliasedType.isInterface()) &&
!aliasedType.isNativeObjectType()) {
functionType = aliasedType;
if (name
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> != null && scope.isGlobal()) {
typeRegistry.declareType(name, functionType.getInstanceType());
}
}
}
}
if (functionType == null) {
Node errorRoot = rValue == null ? lvalueNode : rValue;
boolean isFnLiteral =
rValue != null && rValue.isFunction();
Node fnRoot = isFnLiteral ? rValue : null;
Node parametersNode = isFnLiteral ?
rValue.getFirstChild().getNext() : null;
if (info != null && info.hasType()) {
JSType type = info.getType().evaluate(scope, typeRegistry);
// Known to be not null since we have the FUNCTION token there.
type = type.restrictByNotNullOrUndefined();
if (type.isFunctionType()) {
functionType = type.toMaybeFunctionType();
functionType.setJSDocInfo(info);
}
}
if (functionType == null) {
// Find the type of any overridden function.
Node ownerNode = NodeUtil.getBestLValueOwner(lvalueNode);
String ownerName = NodeUtil.getBestLValueName(ownerNode);
Var ownerVar = null;
String propName = null;
ObjectType ownerType = null;
if (ownerName != null) {
ownerVar = scope.getVar(ownerName);
if (ownerVar != null) {
ownerType = ObjectType.cast(ownerVar.getType());
}
if (name != null) {
propName = name.substring(ownerName.length() + 1);
}
}
ObjectType prototypeOwner = getPrototypeOwnerType(ownerType);
TemplateTypeMap prototypeOwnerTypeMap = null;
if (prototypeOwner != null &&
prototypeOwner.getTypeOfThis() != null) {
prototypeOwnerTypeMap =
prototypeOwner.getTypeOfThis().getTemplateTypeMap();
}
FunctionType overriddenType = null;
if (ownerType != null && propName != null) {
overriddenType = findOverriddenFunction(
ownerType, propName, prototypeOwnerTypeMap);
}
FunctionTypeBuilder builder =
new FunctionTypeBuilder(name, compiler, errorRoot, sourceName,
scope)
.setContents(getFunctionAnalysisResults(fnRoot))
.inferFromOverriddenFunction(overriddenType, parametersNode)
.inferTemplateTypeName(info, prototypeOwner)
.inferReturnType(info)
.inferInheritance(info);
// Infer the context type.
boolean searchedForThisType = false;
if (ownerType != null && ownerType.isFunctionPrototypeType() &&
ownerType.getOwnerFunction().hasInstanceType()) {
builder.inferThisType(
info, ownerType.getOwnerFunction().getInstanceType());
searchedForThisType = true;
} else if (ownerNode != null && ownerNode.isThis()) {
// If 'this' has a type, use that instead.
// This is a hack, necessary because CollectProperties (below)
// doesn't run with the scope that it's building,
// so scope.getTypeOfThis() will be wrong.
JSType injectedThisType = ownerNode.getJSType();
builder.inferThisType(
info,
injectedThisType == null ?
scope.getTypeOfThis() : injectedThisType);
searchedForThisType =
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> true;
}
if (!searchedForThisType) {
builder.inferThisType(info);
}
functionType = builder
.inferParameterTypes(parametersNode, info)
.buildAndRegister();
}
}
// all done
return functionType;
}
private ObjectType getPrototypeOwnerType(ObjectType ownerType) {
if (ownerType != null && ownerType.isFunctionPrototypeType()) {
return ownerType.getOwnerFunction();
}
return null;
}
/**
* Find the function that's being overridden on this type, if any.
*/
private FunctionType findOverriddenFunction(
ObjectType ownerType, String propName, TemplateTypeMap typeMap) {
FunctionType result = null;
// First, check to see if the property is implemented
// on a superclass.
JSType propType = ownerType.getPropertyType(propName);
if (propType != null && propType.isFunctionType()) {
result = propType.toMaybeFunctionType();
} else {
// If it's not, then check to see if it's implemented
// on an implemented interface.
for (ObjectType iface :
ownerType.getCtorImplementedInterfaces()) {
propType = iface.getPropertyType(propName);
if (propType != null && propType.isFunctionType()) {
result = propType.toMaybeFunctionType();
break;
}
}
}
if (result != null && typeMap != null && !typeMap.isEmpty()) {
result = result.visit(
new TemplateTypeMapReplacer(typeRegistry, typeMap))
.toMaybeFunctionType();
}
return result;
}
/**
* Creates a new enum type, based on the given nodes.
*
* This handles two cases that are semantically very different, but
* are not mutually exclusive:
* - An object literal that needs an enum type attached to it.
* - An assignment expression with an enum tag in the JsDoc.
*
* This function will always create an enum type, so only call it if
* you're sure that's what you want.
*
* @param rValue The node of the enum.
* @param name The enum's name
* @param info The {@link JSDocInfo} attached to the enum definition.
* @param lValueNode The node where this function is being
* assigned.
*/
private EnumType createEnumTypeFromNodes(Node rValue, String name,
JSDocInfo info, Node lValueNode) {
Preconditions.checkNotNull(info);
Preconditions.checkState(info.hasEnumParameterType());
EnumType enumType = null;
if (rValue != null && rValue.isQualifiedName()) {
// Handle an aliased enum.
Var var = scope.getVar(rValue.getQualifiedName());
if (var != null && var.getType() instanceof EnumType) {
enumType = (EnumType) var.getType();
}
}
if (enumType == null) {
JSType elementsType =
info.getEnumParameterType().evaluate(scope, typeRegistry);
enumType = typeRegistry.createEnumType(name, rValue, elementsType);
if (rValue != null && rValue.isObjectLit()) {
// collect enum elements
Node key = rValue.getFirstChild
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>();
while (key != null) {
String keyName = NodeUtil.getStringValue(key);
if (keyName == null) {
// GET and SET don't have a String value;
compiler.report(
JSError.make(sourceName, key, ENUM_NOT_CONSTANT, keyName));
} else if (!codingConvention.isValidEnumKey(keyName)) {
compiler.report(
JSError.make(sourceName, key, ENUM_NOT_CONSTANT, keyName));
} else {
enumType.defineElement(keyName, key);
}
key = key.getNext();
}
}
}
if (name != null && scope.isGlobal()) {
typeRegistry.declareType(name, enumType.getElementsType());
}
return enumType;
}
/**
* Defines a typed variable. The defining node will be annotated with the
* variable's type or {@code null} if its type is inferred.
* @param name the defining node. It must be a {@link Token#NAME}.
* @param parent the {@code name}'s parent.
* @param type the variable's type. It may be {@code null}, in which case
* the variable's type will be inferred.
*/
private void defineSlot(Node name, Node parent, JSType type) {
defineSlot(name, parent, type, type == null);
}
/**
* Defines a typed variable. The defining node will be annotated with the
* variable's type of {@link JSTypeNative#UNKNOWN_TYPE} if its type is
* inferred.
*
* Slots may be any variable or any qualified name in the global scope.
*
* @param n the defining NAME or GETPROP node.
* @param parent the {@code n}'s parent.
* @param type the variable's type. It may be {@code null} if
* {@code inferred} is {@code true}.
*/
void defineSlot(Node n, Node parent, JSType type, boolean inferred) {
Preconditions.checkArgument(inferred || type != null);
// Only allow declarations of NAMEs and qualified names.
// Object literal keys will have to compute their names themselves.
if (n.isName()) {
Preconditions.checkArgument(
parent.isFunction() ||
parent.isVar() ||
parent.isParamList() ||
parent.isCatch());
} else {
Preconditions.checkArgument(
n.isGetProp() &&
(parent.isAssign() ||
parent.isExprResult()));
}
defineSlot(n, parent, n.getQualifiedName(), type, inferred);
}
/**
* Defines a symbol in the current scope.
*
* @param n the defining NAME or GETPROP or object literal key node.
* @param parent the {@code n}'s parent.
* @param variableName The name that this should be known by.
* @param type the variable's type. It may be {@code null} if
* {@code inferred} is {@code true}.
* @param inferred Whether the type is inferred or declared.
*/
void defineSlot(Node n, Node parent, String variableName,
JSType type, boolean inferred) {
Preconditions.checkArgument(!variableName.isEmpty());
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> boolean isGlobalVar = n.isName() && scope.isGlobal();
boolean shouldDeclareOnGlobalThis =
isGlobalVar &&
(parent.isVar() ||
parent.isFunction());
// If n is a property, then we should really declare it in the
// scope where the root object appears. This helps out people
// who declare "global" names in an anonymous namespace.
Scope scopeToDeclareIn = scope;
if (n.isGetProp() && !scope.isGlobal() &&
isQnameRootedInGlobalScope(n)) {
Scope globalScope = scope.getGlobalScope();
// don't try to declare in the global scope if there's
// already a symbol there with this name.
if (!globalScope.isDeclared(variableName, false)) {
scopeToDeclareIn = scope.getGlobalScope();
}
}
// The input may be null if we are working with a AST snippet. So read
// the extern info from the node.
Var newVar = null;
// declared in closest scope?
CompilerInput input = compiler.getInput(inputId);
if (scopeToDeclareIn.isDeclared(variableName, false)) {
Var oldVar = scopeToDeclareIn.getVar(variableName);
newVar = validator.expectUndeclaredVariable(
sourceName, input, n, parent, oldVar, variableName, type);
} else {
if (type != null) {
setDeferredType(n, type);
}
newVar =
scopeToDeclareIn.declare(variableName, n, type, input, inferred);
if (type instanceof EnumType) {
Node initialValue = newVar.getInitialValue();
boolean isValidValue = initialValue != null &&
(initialValue.isObjectLit() ||
initialValue.isQualifiedName());
if (!isValidValue) {
compiler.report(JSError.make(sourceName, n, ENUM_INITIALIZER));
}
}
}
// We need to do some additional work for constructors and interfaces.
FunctionType fnType = JSType.toMaybeFunctionType(type);
if (fnType != null &&
// We don't want to look at empty function types.
!type.isEmptyType()) {
// We want to make sure that when we declare a new instance type
// (with @constructor) that there's actually a ctor for it.
// This doesn't apply to structural constructors (like
// function(new:Array). Checking the constructed type against
// the variable name is a sufficient check for this.
if ((fnType.isConstructor() || fnType.isInterface()) &&
variableName.equals(fnType.getReferenceName())) {
finishConstructorDefinition(n, variableName, fnType, scopeToDeclareIn,
input, newVar);
}
}
if (shouldDeclareOnGlobalThis) {
ObjectType globalThis =
typeRegistry.getNativeObjectType(GLOBAL_THIS);
if (inferred) {
globalThis.defineInferredProperty(variableName,
type == null ?
getNativeType(JSTypeNative.NO_TYPE) :
type,
n);
} else {
globalThis.defineDeclaredProperty(variableName, type, n);
}
}
if (isGlobalVar && "Window".equals(variableName)
&& type
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> != null
&& type.isFunctionType()
&& type.isConstructor()) {
FunctionType globalThisCtor =
typeRegistry.getNativeObjectType(GLOBAL_THIS).getConstructor();
globalThisCtor.getInstanceType().clearCachedValues();
globalThisCtor.getPrototype().clearCachedValues();
globalThisCtor
.setPrototypeBasedOn((type.toMaybeFunctionType()).getInstanceType());
}
}
private void finishConstructorDefinition(
Node n, String variableName, FunctionType fnType,
Scope scopeToDeclareIn, CompilerInput input, Var newVar) {
// Declare var.prototype in the scope chain.
FunctionType superClassCtor = fnType.getSuperClassConstructor();
Property prototypeSlot = fnType.getSlot("prototype");
// When we declare the function prototype implicitly, we
// want to make sure that the function and its prototype
// are declared at the same node. We also want to make sure
// that the if a symbol has both a Var and a JSType, they have
// the same node.
//
// This consistency is helpful to users of SymbolTable,
// because everything gets declared at the same place.
prototypeSlot.setNode(n);
String prototypeName = variableName + ".prototype";
// There are some rare cases where the prototype will already
// be declared. See TypedScopeCreatorTest#testBogusPrototypeInit.
// Fortunately, other warnings will complain if this happens.
Var prototypeVar = scopeToDeclareIn.getVar(prototypeName);
if (prototypeVar != null && prototypeVar.scope == scopeToDeclareIn) {
scopeToDeclareIn.undeclare(prototypeVar);
}
scopeToDeclareIn.declare(prototypeName,
n, prototypeSlot.getType(), input,
/* declared iff there's an explicit supertype */
superClassCtor == null ||
superClassCtor.getInstanceType().isEquivalentTo(
getNativeType(OBJECT_TYPE)));
// Make sure the variable is initialized to something if
// it constructs itself.
if (newVar.getInitialValue() == null &&
!n.isFromExterns()) {
compiler.report(
JSError.make(sourceName, n,
fnType.isConstructor() ?
CTOR_INITIALIZER : IFACE_INITIALIZER,
variableName));
}
}
/**
* Check if the given node is a property of a name in the global scope.
*/
private boolean isQnameRootedInGlobalScope(Node n) {
Scope scope = getQnameRootScope(n);
return scope != null && scope.isGlobal();
}
/**
* Return the scope for the name of the given node.
*/
private Scope getQnameRootScope(Node n) {
Node root = NodeUtil.getRootOfQualifiedName(n);
if (root.isName()) {
Var var = scope.getVar(root.getString());
if (var != null) {
return var.getScope();
}
}
return null;
}
/**
* Look for a type declaration on a property assignment
* (in an ASSIGN or an object literal key).
* @param info The doc info for this property.
* @param lValue The l-value node.
* @param rValue The node that {@code n} is being initialized
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> to,
* or {@code null} if this is a stub declaration.
*/
private JSType getDeclaredType(JSDocInfo info, Node lValue,
@Nullable Node rValue) {
if (info != null && info.hasType()) {
return getDeclaredTypeInAnnotation(lValue, info);
} else if (rValue != null && rValue.isFunction() &&
shouldUseFunctionLiteralType(
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
}
// Check if this is constant, and if it has a known type.
if (isConstantSymbol(info, lValue)) {
if (rValue != null) {
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo != null && rValueInfo.hasType()) {
// If rValue has a type-cast, we use the type in the type-cast.
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType() != null
&& !rValue.getJSType().isUnknownType()) {
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
&& firstClause.getString().equals(lValue.getString());
if (namesMatch && secondClause.getJSType() != null
&& !secondClause.getJSType().isUnknownType()) {
return secondClause.getJSType();
}
}
}
}
return getDeclaredTypeInAnnotation(lValue, info);
}
private FunctionType getFunctionType(@Nullable Var v) {
JSType t = v == null ? null : v.getType();
ObjectType o = t == null ? null : t.dereference();
return JSType.toMaybeFunctionType(o);
}
/**
* Look for calls that set a delegate method's calling convention.
*/
private void checkForCallingConventionDefiningCalls(
Node n, Map<String, String> delegateCallingConventions) {
codingConvention.checkForCallingConventionDefiningCalls(n,
delegateCallingConventions);
}
/**
* Look for class-defining calls.
* Because JS has no 'native' syntax for defining classes
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>,
* this is often very coding-convention dependent and business-logic heavy.
*/
private void checkForClassDefiningCalls(NodeTraversal t, Node n) {
SubclassRelationship relationship =
codingConvention.getClassesDefinedByCall(n);
if (relationship != null) {
FunctionType superCtor = getFunctionType(
scope.getVar(relationship.superclassName));
FunctionType subCtor = getFunctionType(
scope.getVar(relationship.subclassName));
if (superCtor != null && superCtor.isConstructor() &&
subCtor != null && subCtor.isConstructor()) {
ObjectType superClass = superCtor.getInstanceType();
ObjectType subClass = subCtor.getInstanceType();
// superCtor and subCtor might be structural constructors
// (like {function(new:Object)}) so we need to resolve them back
// to the original ctor objects.
superCtor = superClass.getConstructor();
subCtor = subClass.getConstructor();
if (relationship.type == SubclassType.INHERITS &&
!superClass.isEmptyType() && !subClass.isEmptyType()) {
validator.expectSuperType(t, n, superClass, subClass);
}
if (superCtor != null && subCtor != null) {
codingConvention.applySubclassRelationship(
superCtor, subCtor, relationship.type);
}
}
}
String singletonGetterClassName =
codingConvention.getSingletonGetterClassName(n);
if (singletonGetterClassName != null) {
ObjectType objectType = ObjectType.cast(
typeRegistry.getType(singletonGetterClassName));
if (objectType != null) {
FunctionType functionType = objectType.getConstructor();
if (functionType != null) {
FunctionType getterType =
typeRegistry.createFunctionType(objectType);
codingConvention.applySingletonGetter(functionType, getterType,
objectType);
}
}
}
DelegateRelationship delegateRelationship =
codingConvention.getDelegateRelationship(n);
if (delegateRelationship != null) {
applyDelegateRelationship(delegateRelationship);
}
ObjectLiteralCast objectLiteralCast =
codingConvention.getObjectLiteralCast(n);
if (objectLiteralCast != null) {
if (objectLiteralCast.diagnosticType == null) {
ObjectType type = ObjectType.cast(
typeRegistry.getType(objectLiteralCast.typeName));
if (type != null && type.getConstructor() != null) {
setDeferredType(objectLiteralCast.objectNode, type);
objectLiteralCast.objectNode.putBooleanProp(
Node.REFLECTED_OBJECT, true);
} else {
compiler.report(JSError.make(t.getSourceName(), n,
CONSTRUCTOR_EXPECTED));
}
} else {
compiler.report(JSError.make(t.getSourceName(), n,
objectLiteralCast.diagnosticType));
}
}
}
/**
* Apply special properties that only apply to delegates.
*/
private void applyDelegateRelationship(
DelegateRelationship delegateRelationship) {
ObjectType delegatorObject = ObjectType.cast(
typeRegistry.getType(delegateRelationship.delegator));
ObjectType delegateBaseObject = ObjectType.cast(
typeRegistry.getType(delegateRelationship.delegateBase));
ObjectType delegateSuperObject = ObjectType.cast(
typeRegistry.getType(codingConvention.getDelegateSuperclassName()));
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>(propName)) {
Var qVar = scope.getVar(qName);
if (qVar != null) {
// If the programmer has declared that F inherits from Super,
// and they assign F.prototype to an object literal,
// then they are responsible for making sure that the object literal's
// implicit prototype is set up appropriately. We just obey
// the @extends tag.
ObjectType qVarType = ObjectType.cast(qVar.getType());
if (qVarType != null &&
rhsValue != null &&
rhsValue.isObjectLit()) {
typeRegistry.resetImplicitPrototype(
rhsValue.getJSType(), qVarType.getImplicitPrototype());
} else if (!qVar.isTypeInferred()) {
// If the programmer has declared that F inherits from Super,
// and they assign F.prototype to some arbitrary expression,
// there's not much we can do. We just ignore the expression,
// and hope they've annotated their code in a way to tell us
// what props are going to be on that prototype.
return;
}
qVar.getScope().undeclare(qVar);
}
}
if (valueType == null) {
if (parent.isExprResult()) {
stubDeclarations.add(new StubDeclaration(
n,
t.getInput() != null && t.getInput().isExtern(),
ownerName));
}
return;
}
boolean inferred = isQualifiedNameInferred(
qName, n, info, rhsValue, valueType);
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
ownerType.defineDeclaredProperty(propName, valueType, n);
}
}
// If the property is already declared, the error will be
// caught when we try to declare it in the current scope.
defineSlot(n, parent, valueType, inferred);
} else if (rhsValue != null && rhsValue.isTrue()) {
// We declare these for delegate proxy method properties.
ObjectType ownerType = getObjectSlot(ownerName);
FunctionType ownerFnType = JSType.toMaybeFunctionType(ownerType);
if (ownerFnType != null) {
JSType ownerTypeOfThis = ownerFnType.getTypeOfThis();
String delegateName = codingConvention.getDelegateSuperclassName();
JSType delegateType = delegateName == null ?
null : typeRegistry.getType(delegateName);
if (delegateType != null &&
ownerTypeOfThis.isSubtype(delegateType)) {
defineSlot(n, parent, getNativeType(BOOLEAN_TYPE), true);
}
}
}
}
/**
* Determines whether a qualified name is inferred.
* NOTE(nicksantos): Determining whether a property is declared or not
* is really really ob
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>noxious.
*
* The problem is that there are two (equally valid) coding styles:
*
* (function() {
* /* The authoritative definition of goog.bar. /
* goog.bar = function() {};
* })();
*
* function f() {
* goog.bar();
* /* Reset goog.bar to a no-op. /
* goog.bar = function() {};
* }
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
// Check if this is in a conditional block.
// Functions assigned in conditional blocks are inferred.
for (Node current = n.getParent();
!(current.isScript() || current.isFunction());
current = current.getParent()) {
if (NodeUtil.isControlStructure(current)) {
return true;
}
}
// Check if this is assigned in an inner scope.
// Functions assigned in inner scopes are inferred.
AstFunctionContents contents =
getFunctionAnalysisResults(scope.getRootNode());
if (contents == null ||
!contents.getEscapedQualifiedNames().contains(qName)) {
return false;
}
}
}
return inferred;
}
private boolean isConstantSymbol(JSDocInfo info, Node node) {
if (info != null && info.isConstant()) {
return true;
}
switch (node.getType()) {
case Token.NAME:
return NodeUtil.isConstantByConvention(
compiler.getCodingConvention(), node, node.getParent());
case Token.GETPROP:
return node.isQualifiedName() && NodeUtil.isConstantByConvention(
compiler.getCodingConvention(), node.getLastChild(), node);
}
return false;
}
/**
* Find the ObjectType associated with the given slot.
* @param slotName The name of the slot to find the type in.
* @return An object type, or null if this slot does not contain an object.
*/
private ObjectType getObjectSlot(String slotName) {
Var ownerVar = scope.getVar(slotName);
if (ownerVar != null) {
JSType ownerVarType = ownerVar.getType();
return ObjectType.cast(ownerVarType == null ?
null : ownerVarType.restrictByNotNullOrUndefined());
}
return null;
}
/**
* Resolve any stub declarations to unknown types if we could not
* find types for them during traversal.
*/
void resolveStubDeclarations() {
for (StubDeclaration stub : stubDeclarations) {
Node n = stub.node;
Node parent = n.getParent();
String qName = n.getQualifiedName();
String propName = n.getLastChild().getString();
String ownerName = stub.ownerName;
boolean isExtern = stub.isExtern;
if (scope.isDeclared(qName, false)) {
continue;
}
// If we see a stub property, make sure to register this property
// in the type registry.
ObjectType ownerType = getObjectSlot(ownerName);
defineSlot(n, parent, unknownType, true);
if (ownerType != null &&
(isExtern || ownerType.isFunctionPrototypeType())) {
// If this is a stub for
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> a prototype, just declare it
// as an unknown type. These are seen often in externs.
ownerType.defineInferredProperty(
propName, unknownType, n);
} else {
typeRegistry.registerPropertyOnType(
propName, ownerType == null ? unknownType : ownerType);
}
}
}
/**
* Collects all declared properties in a function, and
* resolves them relative to the global scope.
*/
private final class CollectProperties
extends AbstractShallowStatementCallback {
private final JSType thisType;
CollectProperties(JSType thisType) {
this.thisType = thisType;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isExprResult()) {
Node child = n.getFirstChild();
switch (child.getType()) {
case Token.ASSIGN:
maybeCollectMember(child.getFirstChild(), child,
child.getLastChild());
break;
case Token.GETPROP:
maybeCollectMember(child, child, null);
break;
}
}
}
private void maybeCollectMember(Node member,
Node nodeWithJsDocInfo, @Nullable Node value) {
JSDocInfo info = nodeWithJsDocInfo.getJSDocInfo();
// Do nothing if there is no JSDoc type info, or
// if the node is not a member expression, or
// if the member expression is not of the form: this.someProperty.
if (info == null ||
!member.isGetProp() ||
!member.getFirstChild().isThis()) {
return;
}
member.getFirstChild().setJSType(thisType);
// TODO(johnlenz): We are evaluating these values in the wrong scope:
// https://code.google.com/p/closure-compiler/issues/detail?id=926
JSType thisObjectType = thisType.toObjectType();
if (thisObjectType != null) {
ImmutableList<TemplateType> keys =
thisObjectType.getTemplateTypeMap().getTemplateKeys();
typeRegistry.setTemplateTypeNames(keys);
}
JSType jsType = getDeclaredType(info, member, value);
if (thisObjectType != null) {
typeRegistry.clearTemplateTypeNames();
}
Node name = member.getLastChild();
if (jsType != null &&
(name.isName() || name.isString()) &&
thisType.toObjectType() != null) {
thisType.toObjectType().defineDeclaredProperty(
name.getString(),
jsType,
member);
}
}
} // end CollectProperties
}
/**
* A stub declaration without any type information.
*/
private static final class StubDeclaration {
private final Node node;
private final boolean isExtern;
private final String ownerName;
private StubDeclaration(Node node, boolean isExtern, String ownerName) {
this.node = node;
this.isExtern = isExtern;
this.ownerName = ownerName;
}
}
/**
* A shallow traversal of the global scope to build up all classes,
* functions, and methods.
*/
private final class GlobalScopeBuilder extends AbstractScopeBuilder {
private GlobalScopeBuilder(Scope scope) {
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>
super(scope);
}
/**
* Visit a node in the global scope, and add anything it declares to the
* global symbol table.
*
* @param t The current traversal.
* @param n The node being visited.
* @param parent The parent of n
*/
@Override public void visit(NodeTraversal t, Node n, Node parent) {
super.visit(t, n, parent);
switch (n.getType()) {
case Token.VAR:
// Handle typedefs.
if (n.hasOneChild()) {
checkForTypedef(t, n.getFirstChild(), n.getJSDocInfo());
}
break;
}
}
@Override
void maybeDeclareQualifiedName(
NodeTraversal t, JSDocInfo info,
Node n, Node parent, Node rhsValue) {
checkForTypedef(t, n, info);
super.maybeDeclareQualifiedName(t, info, n, parent, rhsValue);
}
/**
* Handle typedefs.
* @param t The current traversal.
* @param candidate A qualified name node.
* @param info JSDoc comments.
*/
private void checkForTypedef(
NodeTraversal t, Node candidate, JSDocInfo info) {
if (info == null || !info.hasTypedefType()) {
return;
}
String typedef = candidate.getQualifiedName();
if (typedef == null) {
return;
}
// TODO(nicksantos|user): This is a terrible, terrible hack
// to bail out on recursive typedefs. We'll eventually need
// to handle these properly.
typeRegistry.declareType(typedef, unknownType);
JSType realType = info.getTypedefType().evaluate(scope, typeRegistry);
if (realType == null) {
compiler.report(
JSError.make(
t.getSourceName(), candidate, MALFORMED_TYPEDEF, typedef));
}
typeRegistry.overwriteDeclaredType(typedef, realType);
if (candidate.isGetProp()) {
defineSlot(candidate, candidate.getParent(),
getNativeType(NO_TYPE), false);
}
}
} // end GlobalScopeBuilder
/**
* A shallow traversal of a local scope to find all arguments and
* local variables.
*/
private final class LocalScopeBuilder extends AbstractScopeBuilder {
/**
* @param scope The scope that we're building.
*/
private LocalScopeBuilder(Scope scope) {
super(scope);
}
/**
* Traverse the scope root and build it.
*/
void build() {
NodeTraversal.traverse(compiler, scope.getRootNode(), this);
AstFunctionContents contents =
getFunctionAnalysisResults(scope.getRootNode());
if (contents != null) {
for (String varName : contents.getEscapedVarNames()) {
Var v = scope.getVar(varName);
Preconditions.checkState(v.getScope() == scope);
v.markEscaped();
}
for (Multiset.Entry<String> entry :
contents.getAssignedNameCounts().entrySet()) {
Var v = scope.getVar(entry.getElement());
Preconditions.checkState(v.getScope() == scope);
if (entry.getCount() == 1) {
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>
v.markAssignedExactlyOnce();
}
}
}
}
/**
* Visit a node in a local scope, and add any local variables or catch
* parameters into the local symbol table.
*
* @param t The node traversal.
* @param n The node being visited.
* @param parent The parent of n
*/
@Override public void visit(NodeTraversal t, Node n, Node parent) {
if (n == scope.getRootNode()) {
return;
}
if (n.isParamList() && parent == scope.getRootNode()) {
handleFunctionInputs(parent);
return;
}
super.visit(t, n, parent);
}
/** Handle bleeding functions and function parameters. */
private void handleFunctionInputs(Node fnNode) {
// Handle bleeding functions.
Node fnNameNode = fnNode.getFirstChild();
String fnName = fnNameNode.getString();
if (!fnName.isEmpty()) {
Scope.Var fnVar = scope.getVar(fnName);
if (fnVar == null ||
// Make sure we're not touching a native function. Native
// functions aren't bleeding, but may not have a declaration
// node.
(fnVar.getNameNode() != null &&
// Make sure that the function is actually bleeding by checking
// if has already been declared.
fnVar.getInitialValue() != fnNode)) {
defineSlot(fnNameNode, fnNode, fnNode.getJSType(), false);
}
}
declareArguments(fnNode);
}
/**
* Declares all of a function's arguments.
*/
private void declareArguments(Node functionNode) {
Node astParameters = functionNode.getFirstChild().getNext();
Node iifeArgumentNode = null;
if (NodeUtil.isCallOrNewTarget(functionNode)) {
iifeArgumentNode = functionNode.getNext();
}
FunctionType functionType =
JSType.toMaybeFunctionType(functionNode.getJSType());
if (functionType != null) {
Node jsDocParameters = functionType.getParametersNode();
if (jsDocParameters != null) {
Node jsDocParameter = jsDocParameters.getFirstChild();
for (Node astParameter : astParameters.children()) {
JSType paramType = jsDocParameter == null ?
unknownType : jsDocParameter.getJSType();
boolean inferred = paramType == null || paramType == unknownType;
if (iifeArgumentNode != null && inferred) {
String argumentName = iifeArgumentNode.getQualifiedName();
Var argumentVar =
argumentName == null || scope.getParent() == null
? null : scope.getParent().getVar(argumentName);
if (argumentVar != null && !argumentVar.isTypeInferred()) {
paramType = argumentVar.getType();
}
}
if (paramType == null) {
paramType = unknownType;
}
defineSlot(astParameter, functionNode, paramType, inferred);
if (jsDocParameter != null) {
jsDocParameter = jsDocParameter.getNext();
}
if (iifeArgumentNode != null) {
iifeArgumentNode = iifeArgumentNode.getNext();
}
}
}
}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> // end declareArguments
} // end LocalScopeBuilder
/**
* Does a first-order function analysis that just looks at simple things
* like what variables are escaped, and whether 'this' is used.
*/
private static class FirstOrderFunctionAnalyzer
extends AbstractScopedCallback implements CompilerPass {
private final AbstractCompiler compiler;
private final Map<Node, AstFunctionContents> data;
FirstOrderFunctionAnalyzer(
AbstractCompiler compiler, Map<Node, AstFunctionContents> outParam) {
this.compiler = compiler;
this.data = outParam;
}
@Override public void process(Node externs, Node root) {
if (externs == null) {
NodeTraversal.traverse(compiler, root, this);
} else {
NodeTraversal.traverseRoots(
compiler, ImmutableList.of(externs, root), this);
}
}
@Override public void enterScope(NodeTraversal t) {
if (!t.inGlobalScope()) {
Node n = t.getScopeRoot();
data.put(n, new AstFunctionContents(n));
}
}
@Override public void visit(NodeTraversal t, Node n, Node parent) {
if (t.inGlobalScope()) {
return;
}
if (n.isReturn() && n.getFirstChild() != null) {
data.get(t.getScopeRoot()).recordNonEmptyReturn();
}
if (t.getScopeDepth() <= 1) {
// The first-order function analyzer looks at two types of variables:
//
// 1) Local variables that are assigned in inner scopes ("escaped vars")
//
// 2) Local variables that are assigned more than once.
//
// We treat all global variables as escaped by default, so there's
// no reason to do this extra computation for them.
return;
}
if (n.isName() && NodeUtil.isLValue(n) &&
// Be careful of bleeding functions, which create variables
// in the inner scope, not the scope where the name appears.
!NodeUtil.isBleedingFunctionName(n)) {
String name = n.getString();
Scope scope = t.getScope();
Var var = scope.getVar(name);
if (var != null) {
Scope ownerScope = var.getScope();
if (ownerScope.isLocal()) {
data.get(ownerScope.getRootNode()).recordAssignedName(name);
}
if (scope != ownerScope && ownerScope.isLocal()) {
data.get(ownerScope.getRootNode()).recordEscapedVarName(name);
}
}
} else if (n.isGetProp() && n.isUnscopedQualifiedName() &&
NodeUtil.isLValue(n)) {
String name = NodeUtil.getRootOfQualifiedName(n).getString();
Scope scope = t.getScope();
Var var = scope.getVar(name);
if (var != null) {
Scope ownerScope = var.getScope();
if (scope != ownerScope && ownerScope.isLocal()) {
data.get(ownerScope.getRootNode())
.recordEscapedQualifiedName(n.getQualifiedName());
}
}
}
}
}
private AstFunctionContents getFunctionAnalysisResults(@Nullable Node
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>/*
* Copyright 2004 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.*;
/**
* Verifies that constants are only assigned a value once.
* e.g. var XX = 5;
* XX = 3; // error!
* XX++; // error!
*
*/
class ConstCheck extends AbstractPostOrderCallback
implements CompilerPass {
static final DiagnosticType CONST_REASSIGNED_VALUE_ERROR =
DiagnosticType.error(
"JSC_CONSTANT_REASSIGNED_VALUE_ERROR",
"constant {0} assigned a value more than once");
private final AbstractCompiler compiler;
private final Set<Scope.Var> initializedConstants;
/**
* Creates an instance.
*/
public ConstCheck(AbstractCompiler compiler) {
this.compiler = compiler;
this.initializedConstants = new HashSet<Scope.Var>();
}
@Override
public void process(Node externs, Node root) {
Preconditions.checkState(compiler.getLifeCycleStage().isNormalized());
NodeTraversal.traverse(compiler, root, this);
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.NAME:
if (parent != null &&
parent.isVar() &&
n.hasChildren()) {
String name = n.getString();
Scope.Var var = t.getScope().getVar(name);
if (isConstant(var)) {
if (initializedConstants.contains(var)) {
reportError(t, n, name);
} else {
initializedConstants.add(var);
}
}
}
break;
case Token.ASSIGN:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.ASSIGN_URSH:
case Token.ASSIGN_ADD:
case Token.ASSIGN_SUB:
case Token.ASSIGN_MUL:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD: {
Node lhs = n.getFirstChild();
if (lhs.isName()) {
String name = lhs.getString();
Scope.Var var = t.getScope().getVar(name);
if (isConstant(var)) {
if (
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>initializedConstants.contains(var)) {
reportError(t, n, name);
} else {
initializedConstants.add(var);
}
}
}
break;
}
case Token.INC:
case Token.DEC: {
Node lhs = n.getFirstChild();
if (lhs.isName()) {
String name = lhs.getString();
Scope.Var var = t.getScope().getVar(name);
if (isConstant(var)) {
reportError(t, n, name);
}
}
break;
}
}
}
/**
* Gets whether a variable is a constant initialized to a literal value at
* the point where it is declared.
*/
private boolean isConstant(Scope.Var var) {
return var != null && var.isConst();
}
/**
* Reports a reassigned constant error.
*/
void reportError(NodeTraversal t, Node n, String name) {
compiler.report(t.makeError(n, CONST_REASSIGNED_VALUE_ERROR, name));
}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> rename = nameGenerator.getName(instanceId, name);
}
idGeneratorMap.put(rename, instanceId);
return rename;
}
}
/**
* @return The serialize map of generators and their ids and their
* replacements.
*/
public String getSerializedIdMappings() {
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, Map<String, String>> replacements :
idGeneratorMaps.entrySet()) {
if (!replacements.getValue().isEmpty()) {
sb.append("[");
sb.append(replacements.getKey());
sb.append("]\n\n");
for (Map.Entry<String, String> replacement :
replacements.getValue().entrySet()) {
sb.append(replacement.getKey());
sb.append(':');
sb.append(replacement.getValue());
sb.append("\n");
}
sb.append("\n");
}
}
return sb.toString();
}
private Map<String, BiMap<String, String>> parsePreviousResults(
String serializedMap) {
//
// The expected format looks like this:
//
// [generatorName]
// someId:someFile:theLine:theColumn
//
//
if (serializedMap == null || serializedMap.isEmpty()) {
return Collections.emptyMap();
}
Map<String, BiMap<String, String>> resultMap = Maps.newHashMap();
BufferedReader reader = new BufferedReader(new StringReader(serializedMap));
BiMap<String, String> currentSectionMap = null;
String line;
int lineIndex = 0;
try {
while ((line = reader.readLine()) != null) {
lineIndex++;
if (line.isEmpty()) {
continue;
}
if (line.charAt(0) == '[') {
String currentSection = line.substring(1, line.length() - 1);
currentSectionMap = resultMap.get(currentSection);
if (currentSectionMap == null) {
currentSectionMap = HashBiMap.create();
resultMap.put(currentSection, currentSectionMap);
} else {
reportInvalidLine(line, lineIndex);
return Collections.emptyMap();
}
} else {
int split = line.indexOf(':');
if (split != -1) {
String name = line.substring(0, split);
String location = line.substring(split + 1, line.length());
currentSectionMap.put(name, location);
} else {
reportInvalidLine(line, lineIndex);
return Collections.emptyMap();
}
}
}
} catch (IOException e) {
JSError.make(INVALID_GENERATOR_ID_MAPPING, e.getMessage());
}
return resultMap;
}
private void reportInvalidLine(String line, int lineIndex) {
JSError.make(INVALID_GENERATOR_ID_MAPPING,
"line(" + line + "): " + lineIndex);
}
String getIdForGeneratorNode(boolean consistent, Node n) {
Preconditions.checkState(n.isString() || n.isStringKey());
if (consistent) {
return n.getString();
} else {
return n.getSourceFileName() + ':' + n.getLineno() + ":" +
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> tracked by MemoizedScopeCreator
*/
static class MemoizedScopeCleanupPass implements HotSwapCompilerPass {
private final AbstractCompiler compiler;
public MemoizedScopeCleanupPass(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void hotSwapScript(Node scriptRoot, Node originalRoot) {
ScopeCreator creator = compiler.getTypedScopeCreator();
if (creator instanceof MemoizedScopeCreator) {
MemoizedScopeCreator scopeCreator = (MemoizedScopeCreator) creator;
String newSrc = scriptRoot.getSourceFileName();
for (Var var : scopeCreator.getAllSymbols()) {
JSType type = var.getType();
if (type != null) {
FunctionType fnType = type.toMaybeFunctionType();
if (fnType != null
&& newSrc.equals(NodeUtil.getSourceName(fnType.getSource()))) {
fnType.setSource(null);
}
}
}
scopeCreator.removeScopesForScript(originalRoot.getSourceFileName());
}
}
@Override
public void process(Node externs, Node root) {
// MemoizedScopeCleanupPass should not do work during process.
}
}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> SubclassRelationship getClassesDefinedByCall(Node callNode) {
return nextConvention.getClassesDefinedByCall(callNode);
}
@Override
public boolean isSuperClassReference(String propertyName) {
return nextConvention.isSuperClassReference(propertyName);
}
@Override
public String extractClassNameIfProvide(Node node, Node parent) {
return nextConvention.extractClassNameIfProvide(node, parent);
}
@Override
public String extractClassNameIfRequire(Node node, Node parent) {
return nextConvention.extractClassNameIfRequire(node, parent);
}
@Override
public String getExportPropertyFunction() {
return nextConvention.getExportPropertyFunction();
}
@Override
public String getExportSymbolFunction() {
return nextConvention.getExportSymbolFunction();
}
@Override
public List<String> identifyTypeDeclarationCall(Node n) {
return nextConvention.identifyTypeDeclarationCall(n);
}
@Override
public void applySubclassRelationship(FunctionType parentCtor,
FunctionType childCtor, SubclassType type) {
nextConvention.applySubclassRelationship(
parentCtor, childCtor, type);
}
@Override
public String getAbstractMethodName() {
return nextConvention.getAbstractMethodName();
}
@Override
public String getSingletonGetterClassName(Node callNode) {
return nextConvention.getSingletonGetterClassName(callNode);
}
@Override
public void applySingletonGetter(FunctionType functionType,
FunctionType getterType, ObjectType objectType) {
nextConvention.applySingletonGetter(
functionType, getterType, objectType);
}
@Override
public boolean isInlinableFunction(Node n) {
return nextConvention.isInlinableFunction(n);
}
@Override
public DelegateRelationship getDelegateRelationship(Node callNode) {
return nextConvention.getDelegateRelationship(callNode);
}
@Override
public void applyDelegateRelationship(
ObjectType delegateSuperclass, ObjectType delegateBase,
ObjectType delegator, FunctionType delegateProxy,
FunctionType findDelegate) {
nextConvention.applyDelegateRelationship(
delegateSuperclass, delegateBase, delegator,
delegateProxy, findDelegate);
}
@Override
public String getDelegateSuperclassName() {
return nextConvention.getDelegateSuperclassName();
}
@Override
public void checkForCallingConventionDefiningCalls(
Node n, Map<String, String> delegateCallingConventions) {
nextConvention.checkForCallingConventionDefiningCalls(
n, delegateCallingConventions);
}
@Override
public void defineDelegateProxyPrototypeProperties(
JSTypeRegistry registry, StaticScope<JSType> scope,
List<ObjectType> delegateProxyPrototypes,
Map<String, String> delegateCallingConventions) {
nextConvention.defineDelegateProxyPrototypeProperties(
registry, scope, delegateProxyPrototypes, delegateCallingConventions);
}
@Override
public String getGlobalObject() {
return nextConvention.getGlobalObject();
}
@Override
public Collection<AssertionFunctionSpec> getAssertionFunctions() {
return nextConvention.getAssertionFunctions();
}
@Override
public Bind describeFunctionBind(Node n) {
return describeFunctionBind(n, false);
}
@Override
public Bind describeFunctionBind(Node n, boolean useTypeInfo) {
return nextConvention.
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>) {
return null;
}
@Override
public void applyDelegateRelationship(
ObjectType delegateSuperclass, ObjectType delegateBase,
ObjectType delegator, FunctionType delegateProxy,
FunctionType findDelegate) {
// do nothing.
}
@Override
public String getDelegateSuperclassName() {
return null;
}
@Override
public void checkForCallingConventionDefiningCalls(Node n,
Map<String, String> delegateCallingConventions) {
// do nothing.
}
@Override
public void defineDelegateProxyPrototypeProperties(
JSTypeRegistry registry, StaticScope<JSType> scope,
List<ObjectType> delegateProxyPrototypes,
Map<String, String> delegateCallingConventions) {
// do nothing.
}
@Override
public String getGlobalObject() {
return "window";
}
@Override
public boolean isPropertyTestFunction(Node call) {
return false;
}
@Override
public boolean isPrototypeAlias(Node getProp) {
return false;
}
@Override
public ObjectLiteralCast getObjectLiteralCast(Node callNode) {
return null;
}
@Override
public Collection<AssertionFunctionSpec> getAssertionFunctions() {
return Collections.emptySet();
}
@Override
public Bind describeFunctionBind(Node n) {
return describeFunctionBind(n, false);
}
@Override
public Bind describeFunctionBind(Node n, boolean useTypeInfo) {
if (!n.isCall()) {
return null;
}
Node callTarget = n.getFirstChild();
String name = callTarget.getQualifiedName();
if (name != null) {
if (name.equals("Function.prototype.bind.call")) {
// goog.bind(fn, self, args...);
Node fn = callTarget.getNext();
if (fn == null) {
return null;
}
Node thisValue = safeNext(fn);
Node parameters = safeNext(thisValue);
return new Bind(fn, thisValue, parameters);
}
}
if (callTarget.isGetProp()
&& callTarget.getLastChild().getString().equals("bind")) {
Node maybeFn = callTarget.getFirstChild();
JSType maybeFnType = maybeFn.getJSType();
FunctionType fnType = null;
if (useTypeInfo && maybeFnType != null) {
fnType = maybeFnType.restrictByNotNullOrUndefined()
.toMaybeFunctionType();
}
if (fnType != null || maybeFn.isFunction()) {
// (function(){}).bind(self, args...);
Node thisValue = callTarget.getNext();
Node parameters = safeNext(thisValue);
return new Bind(maybeFn, thisValue, parameters);
}
}
return null;
}
@Override
public Collection<String> getIndirectlyDeclaredProperties() {
return ImmutableList.of();
}
private Node safeNext(Node n) {
if (n != null) {
return n.getNext();
}
return null;
}
}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> line = getLineOfOffset(offset);
return offset - lineOffsets[line - 1];
}
/**
* Gets the source line for the indicated line number.
*
* @param lineNumber the line number, 1 being the first line of the file.
* @return The line indicated. Does not include the newline at the end
* of the file. Returns {@code null} if it does not exist,
* or if there was an IO exception.
*/
public String getLine(int lineNumber) {
findLineOffsets();
if (lineNumber > lineOffsets.length) {
return null;
}
if (lineNumber < 1) {
lineNumber = 1;
}
int pos = lineOffsets[lineNumber - 1];
String js = "";
try {
// NOTE(nicksantos): Right now, this is optimized for few warnings.
// This is probably the right trade-off, but will be slow if there
// are lots of warnings in one file.
js = getCode();
} catch (IOException e) {
return null;
}
if (js.indexOf('\n', pos) == -1) {
// If next new line cannot be found, there are two cases
// 1. pos already reaches the end of file, then null should be returned
// 2. otherwise, return the contents between pos and the end of file.
if (pos >= js.length()) {
return null;
} else {
return js.substring(pos, js.length());
}
} else {
return js.substring(pos, js.indexOf('\n', pos));
}
}
/**
* Get a region around the indicated line number. The exact definition of a
* region is implementation specific, but it must contain the line indicated
* by the line number. A region must not start or end by a carriage return.
*
* @param lineNumber the line number, 1 being the first line of the file.
* @return The line indicated. Returns {@code null} if it does not exist,
* or if there was an IO exception.
*/
public Region getRegion(int lineNumber) {
String js = "";
try {
js = getCode();
} catch (IOException e) {
return null;
}
int pos = 0;
int startLine = Math.max(1,
lineNumber - (SOURCE_EXCERPT_REGION_LENGTH + 1) / 2 + 1);
for (int n = 1; n < startLine; n++) {
int nextpos = js.indexOf('\n', pos);
if (nextpos == -1) {
break;
}
pos = nextpos + 1;
}
int end = pos;
int endLine = startLine;
for (int n = 0; n < SOURCE_EXCERPT_REGION_LENGTH; n++, endLine++) {
end = js.indexOf('\n', end);
if (end == -1) {
break;
}
end++;
}
if (lineNumber >= endLine) {
return null;
}
if (end == -1) {
int last = js.length() - 1;
if (js.charAt(last)
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> == '\n') {
return
new SimpleRegion(startLine, endLine, js.substring(pos, last));
} else {
return new SimpleRegion(startLine, endLine, js.substring(pos));
}
} else {
return new SimpleRegion(startLine, endLine, js.substring(pos, end));
}
}
@Override
public String toString() {
return fileName;
}
public static SourceFile fromFile(String fileName, Charset c) {
return builder().withCharset(c).buildFromFile(fileName);
}
public static SourceFile fromFile(String fileName) {
return builder().buildFromFile(fileName);
}
public static SourceFile fromFile(File file, Charset c) {
return builder().withCharset(c).buildFromFile(file);
}
public static SourceFile fromFile(File file) {
return builder().buildFromFile(file);
}
public static SourceFile fromCode(String fileName, String code) {
return builder().buildFromCode(fileName, code);
}
public static SourceFile fromCode(String fileName,
String originalPath, String code) {
return builder().withOriginalPath(originalPath)
.buildFromCode(fileName, code);
}
public static SourceFile fromInputStream(String fileName, InputStream s)
throws IOException {
return builder().buildFromInputStream(fileName, s);
}
public static SourceFile fromInputStream(String fileName,
String originalPath, InputStream s) throws IOException {
return builder().withOriginalPath(originalPath)
.buildFromInputStream(fileName, s);
}
public static SourceFile fromReader(String fileName, Reader r)
throws IOException {
return builder().buildFromReader(fileName, r);
}
public static SourceFile fromGenerator(String fileName,
Generator generator) {
return builder().buildFromGenerator(fileName, generator);
}
/** Create a new builder for source files. */
public static Builder builder() {
return new Builder();
}
/**
* A builder interface for source files.
*
* Allows users to customize the Charset, and the original path of
* the source file (if it differs from the path on disk).
*/
public static class Builder {
private Charset charset = Charsets.UTF_8;
private String originalPath = null;
public Builder() {}
/** Set the charset to use when reading from an input stream or file. */
public Builder withCharset(Charset charset) {
this.charset = charset;
return this;
}
/** Set the original path to use. */
public Builder withOriginalPath(String originalPath) {
this.originalPath = originalPath;
return this;
}
public SourceFile buildFromFile(String fileName) {
return buildFromFile(new File(fileName));
}
public SourceFile buildFromFile(File file) {
return new OnDisk(file, originalPath, charset);
}
public SourceFile buildFromCode(String fileName, String code) {
return new Preloaded(fileName, originalPath, code);
}
public SourceFile buildFromInputStream(String fileName, InputStream s)
throws IOException {
return buildFromCode(fileName,
CharStreams.toString(new InputStreamReader(s, charset)));
}
public SourceFile buildFromReader(String fileName, Reader r)
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>,
// so we'll allow it.
if (n.isEmpty() ||
n.isComma()) {
return;
}
if (parent == null) {
return;
}
// Do not try to remove a block or an expr result. We already handle
// these cases when we visit the child, and the peephole passes will
// fix up the tree in more clever ways when these are removed.
if (n.isExprResult() || n.isBlock()) {
return;
}
// This no-op statement was there so that JSDoc information could
// be attached to the name. This check should not complain about it.
if (n.isQualifiedName() && n.getJSDocInfo() != null) {
return;
}
boolean isResultUsed = NodeUtil.isExpressionResultUsed(n);
boolean isSimpleOp = NodeUtil.isSimpleOperator(n);
if (!isResultUsed &&
(isSimpleOp || !NodeUtil.mayHaveSideEffects(n, t.getCompiler()))) {
String msg = "This code lacks side-effects. Is there a bug?";
if (n.isString()) {
msg = "Is there a missing '+' on the previous line?";
} else if (isSimpleOp) {
msg = "The result of the '" + Token.name(n.getType()).toLowerCase() +
"' operator is not being used.";
}
t.getCompiler().report(
t.makeError(n, level, USELESS_CODE_ERROR, msg));
// TODO(johnlenz): determine if it is necessary to
// try to protect side-effect free statements as well.
if (!NodeUtil.isStatement(n)) {
problemNodes.add(n);
}
}
}
/**
* Protect side-effect free nodes by making them parameters
* to a extern function call. This call will be removed
* after all the optimizations passes have run.
*/
private void protectSideEffects() {
if (!problemNodes.isEmpty()) {
addExtern();
for (Node n : problemNodes) {
Node name = IR.name(PROTECTOR_FN).srcref(n);
name.putBooleanProp(Node.IS_CONSTANT_NAME, true);
Node replacement = IR.call(name).srcref(n);
replacement.putBooleanProp(Node.FREE_CALL, true);
n.getParent().replaceChild(n, replacement);
replacement.addChildToBack(n);
}
compiler.reportCodeChange();
}
}
private void addExtern() {
Node name = IR.name(PROTECTOR_FN);
name.putBooleanProp(Node.IS_CONSTANT_NAME, true);
Node var = IR.var(name);
// Add "@noalias" so we can strip the method when AliasExternals is enabled.
JSDocInfoBuilder builder = new JSDocInfoBuilder(false);
builder.recordNoAlias();
var.setJSDocInfo(builder.build(var));
CompilerInput input = compiler.getSynthesizedExternsInput();
input.getAstRoot(compiler).addChildrenToBack(var);
compiler.reportCodeChange();
}
/**
* Remove
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>/*
* Copyright 2007 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.javascript.rhino.Node;
import java.util.regex.Pattern;
/**
* This describes the Google-specific JavaScript coding conventions.
* Within Google, variable names are semantically significant.
*
*/
public class GoogleCodingConvention extends CodingConventions.Proxy {
private static final long serialVersionUID = 1L;
private static final String OPTIONAL_ARG_PREFIX = "opt_";
private static final String VAR_ARGS_NAME = "var_args";
private static final Pattern ENUM_KEY_PATTERN =
Pattern.compile("[A-Z0-9][A-Z0-9_]*");
/** By default, decorate the ClosureCodingConvention. */
public GoogleCodingConvention() {
this(new ClosureCodingConvention());
}
/** Decorates a wrapped CodingConvention. */
public GoogleCodingConvention(CodingConvention convention) {
super(convention);
}
/**
* {@inheritDoc}
*
* <p>This enforces the Google const name convention, that the first character
* after the last $ must be an upper-case letter and all subsequent letters
* must be upper case. The name must be at least 2 characters long.
*
* <p>Examples:
* <pre>
* aaa Not constant - lower-case letters in the name
* A Not constant - too short
* goog$A Constant - letters after the $ are upper-case.
* AA17 Constant - digits can appear after the first letter
* goog$7A Not constant - first character after the $ must be
* upper case.
* $A Constant - doesn't have to be anything in front of the $
* </pre>
*/
@Override
public boolean isConstant(String name) {
if (name.length() <= 1) {
return false;
}
// In compiled code, '$' is often a namespace delimiter. To allow inlining
// of namespaced constants, we strip off any namespaces here.
int pos = name.lastIndexOf('$');
if (pos >= 0) {
name = name.substring(pos + 1);
if (name.length() == 0) {
return false;
}
}
return isConstantKey(name);
}
@Override
public boolean isConstantKey(String name) {
if (name.isEmpty() || !Character.isUpperCase(name.charAt(0))) {
return false;
}
// hack way of checking that there aren't any lower-case
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>Value.UNKNOWN) {
return val.toBoolean(true) == (Branch.ON_TRUE == branch);
}
}
}
return true;
}
};
/**
* @param level level of severity to report when a missing return statement
* is discovered
*/
CheckMissingReturn(AbstractCompiler compiler, CheckLevel level) {
this.compiler = compiler;
this.level = level;
}
@Override
public void enterScope(NodeTraversal t) {
JSType returnType = explicitReturnExpected(t.getScopeRoot());
if (returnType == null) {
return;
}
if (fastAllPathsReturnCheck(t.getControlFlowGraph())) {
return;
}
CheckPathsBetweenNodes<Node, ControlFlowGraph.Branch> test =
new CheckPathsBetweenNodes<Node, ControlFlowGraph.Branch>(
t.getControlFlowGraph(),
t.getControlFlowGraph().getEntry(),
t.getControlFlowGraph().getImplicitReturn(),
IS_RETURN, GOES_THROUGH_TRUE_CONDITION_PREDICATE);
if (!test.allPathsSatisfyPredicate()) {
compiler.report(
t.makeError(t.getScopeRoot(), level,
MISSING_RETURN_STATEMENT, returnType.toString()));
}
}
/**
* Fast check to see if all execution paths contain a return statement.
* May spuriously report that a return statement is missing.
*
* @return true if all paths return, converse not necessarily true
*/
private static boolean fastAllPathsReturnCheck(ControlFlowGraph<Node> cfg) {
for (DiGraphEdge<Node, Branch> s : cfg.getImplicitReturn().getInEdges()) {
if (!s.getSource().getValue().isReturn()) {
return false;
}
}
return true;
}
@Override
public void exitScope(NodeTraversal t) {
}
@Override
public boolean shouldTraverse(
NodeTraversal nodeTraversal, Node n, Node parent) {
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
}
/**
* Determines if the given scope should explicitly return. All functions
* with non-void or non-unknown return types must have explicit returns.
*
* Exception: Constructors which specifically specify a return type are
* used to allow invocation without requiring the "new" keyword. They
* have an implicit return type. See unit tests.
*
* @return If a return type is expected, returns it. Otherwise, returns null.
*/
private JSType explicitReturnExpected(Node scope) {
FunctionType scopeType = JSType.toMaybeFunctionType(scope.getJSType());
if (scopeType == null) {
return null;
}
if (isEmptyFunction(scope)) {
return null;
}
if (scopeType.isConstructor()) {
return null;
}
JSType returnType = scopeType.getReturnType();
if (returnType == null) {
return null;
}
if (!isVoidOrUnknown(returnType)) {
return returnType;
}
return null;
}
/**
* @return {@code true} if function represents a JavaScript function
* with an empty body
*/
private static boolean isEmptyFunction(Node
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>/*
* Copyright 2009 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.javascript.jscomp.Scope.Var;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.jstype.StaticSymbolTable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* Memoize a scope creator.
*
* This allows you to make multiple passes, without worrying about
* the expense of generating Scope objects over and over again.
*
* On the other hand, you also have to be more aware of what your passes
* are doing. Scopes are memoized stupidly, so if the underlying tree
* changes, the scope may be out of sync.
*
* @author nicksantos@google.com (Nick Santos)
*/
class MemoizedScopeCreator
implements ScopeCreator, StaticSymbolTable<Var, Var> {
private final Map<Node, Scope> scopes = Maps.newHashMap();
private final ScopeCreator delegate;
/**
* @param delegate The real source of Scope objects.
*/
MemoizedScopeCreator(ScopeCreator delegate) {
this.delegate = delegate;
}
@Override
public Iterable<Var> getReferences(Var var) {
return ImmutableList.of(var);
}
@Override
public Scope getScope(Var var) {
return var.scope;
}
@Override
public Iterable<Var> getAllSymbols() {
List<Var> vars = Lists.newArrayList();
for (Scope s : scopes.values()) {
Iterables.addAll(vars, s.getAllSymbols());
}
return vars;
}
@Override
public Scope createScope(Node n, Scope parent) {
Scope scope = scopes.get(n);
if (scope == null) {
scope = delegate.createScope(n, parent);
scopes.put(n, scope);
} else {
Preconditions.checkState(parent == scope.getParent());
}
return scope;
}
Collection<Scope> getAllMemoizedScopes() {
return Collections.unmodifiableCollection(scopes.values());
}
Scope getScopeIfMemoized(Node n) {
return
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>.getFirstChild(); c != null; c = c.getNext()) {
if (NodeUtil.isControlStructureCodeBlock(n, c) && !c.isBlock()) {
Node newBlock = IR.block().srcref(n);
n.replaceChild(c, newBlock);
if (!c.isEmpty()) {
newBlock.addChildrenToFront(c);
} else {
newBlock.setWasEmptyNode(true);
}
c = newBlock;
reportChange();
}
}
}
}
/**
* Normalize where annotations appear on the AST. Copies
* around existing JSDoc annotations as well as internal annotations.
*/
static class PrepareAnnotations
implements NodeTraversal.Callback {
PrepareAnnotations() {
}
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.isObjectLit()) {
normalizeObjectLiteralAnnotations(n);
}
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.CALL:
annotateCalls(n);
break;
case Token.FUNCTION:
annotateDispatchers(n, parent);
break;
}
}
private void normalizeObjectLiteralAnnotations(Node objlit) {
Preconditions.checkState(objlit.isObjectLit());
for (Node key = objlit.getFirstChild();
key != null; key = key.getNext()) {
Node value = key.getFirstChild();
normalizeObjectLiteralKeyAnnotations(objlit, key, value);
}
}
/**
* There are two types of calls we are interested in calls without explicit
* "this" values (what we are call "free" calls) and direct call to eval.
*/
private void annotateCalls(Node n) {
Preconditions.checkState(n.isCall());
// Keep track of of the "this" context of a call. A call without an
// explicit "this" is a free call.
Node first = n.getFirstChild();
// ignore cast nodes.
while (first.isCast()) {
first = first.getFirstChild();
}
if (!NodeUtil.isGet(first)) {
n.putBooleanProp(Node.FREE_CALL, true);
}
// Keep track of the context in which eval is called. It is important
// to distinguish between "(0, eval)()" and "eval()".
if (first.isName() &&
"eval".equals(first.getString())) {
first.putBooleanProp(Node.DIRECT_EVAL, true);
}
}
/**
* Translate dispatcher info into the property expected node.
*/
private void annotateDispatchers(Node n, Node parent) {
Preconditions.checkState(n.isFunction());
if (parent.getJSDocInfo() != null
&& parent.getJSDocInfo().isJavaDispatch()) {
if (parent.isAssign()) {
Preconditions.checkState(parent.getLastChild() == n);
n.putBooleanProp(Node.IS_DISPATCHER, true);
}
}
}
/**
* In the AST that Rhino gives us, it needs to make a distinction
* between JsDoc on the object literal node
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> n, JSType rightType,
JSType leftType, Node owner, String propName) {
// The NoType check is a hack to make typedefs work OK.
if (!leftType.isNoType() && !rightType.isSubtype(leftType)) {
// Do not type-check interface methods, because we expect that
// they will have dummy implementations that do not match the type
// annotations.
JSType ownerType = getJSType(owner);
if (ownerType.isFunctionPrototypeType()) {
FunctionType ownerFn = ownerType.toObjectType().getOwnerFunction();
if (ownerFn.isInterface() &&
rightType.isFunctionType() && leftType.isFunctionType()) {
return true;
}
}
mismatch(t, n,
"assignment to property " + propName + " of " +
getReadableJSTypeName(owner, true),
rightType, leftType);
return false;
}
return true;
}
/**
* Expect that the first type can be assigned to a symbol of the second
* type.
*
* @param t The node traversal.
* @param n The node to issue warnings on.
* @param rightType The type on the RHS of the assign.
* @param leftType The type of the symbol on the LHS of the assign.
* @param msg An extra message for the mismatch warning, if necessary.
* @return True if the types matched, false otherwise.
*/
boolean expectCanAssignTo(NodeTraversal t, Node n, JSType rightType,
JSType leftType, String msg) {
if (!rightType.isSubtype(leftType)) {
mismatch(t, n, msg, rightType, leftType);
return false;
}
return true;
}
/**
* Expect that the type of an argument matches the type of the parameter
* that it's fulfilling.
*
* @param t The node traversal.
* @param n The node to issue warnings on.
* @param argType The type of the argument.
* @param paramType The type of the parameter.
* @param callNode The call node, to help with the warning message.
* @param ordinal The argument ordinal, to help with the warning message.
*/
void expectArgumentMatchesParameter(NodeTraversal t, Node n, JSType argType,
JSType paramType, Node callNode, int ordinal) {
if (!argType.isSubtype(paramType)) {
mismatch(t, n,
String.format("actual parameter %d of %s does not match " +
"formal parameter", ordinal,
getReadableJSTypeName(callNode.getFirstChild(), false)),
argType, paramType);
}
}
/**
* Expect that the first type can override a property of the second
* type.
*
* @param t The node traversal.
* @param n The node to issue warnings on.
* @param overridingType The overriding type.
* @param hiddenType The type of the property being overridden.
* @param propertyName The name of the property, for use in the
* warning message.
* @param ownerType The type of the owner of the property, for use
* in the warning message.
*/
void expectCanOverride(NodeTraversal
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> new source info.
*/
Var expectUndeclaredVariable(String sourceName, CompilerInput input,
Node n, Node parent, Var var, String variableName, JSType newType) {
Var newVar = var;
boolean allowDupe = false;
if (n.isGetProp() ||
NodeUtil.isObjectLitKey(n)) {
JSDocInfo info = n.getJSDocInfo();
if (info == null) {
info = parent.getJSDocInfo();
}
allowDupe =
info != null && info.getSuppressions().contains("duplicate");
}
JSType varType = var.getType();
// Only report duplicate declarations that have types. Other duplicates
// will be reported by the syntactic scope creator later in the
// compilation process.
if (varType != null &&
varType != typeRegistry.getNativeType(UNKNOWN_TYPE) &&
newType != null &&
newType != typeRegistry.getNativeType(UNKNOWN_TYPE)) {
// If there are two typed declarations of the same variable, that
// is an error and the second declaration is ignored, except in the
// case of native types. A null input type means that the declaration
// was made in TypedScopeCreator#createInitialScope and is a
// native type. We should redeclare it at the new input site.
if (var.input == null) {
Scope s = var.getScope();
s.undeclare(var);
newVar = s.declare(variableName, n, varType, input, false);
n.setJSType(varType);
if (parent.isVar()) {
if (n.getFirstChild() != null) {
n.getFirstChild().setJSType(varType);
}
} else {
Preconditions.checkState(parent.isFunction());
parent.setJSType(varType);
}
} else {
// Always warn about duplicates if the overridden type does not
// match the original type.
//
// If the types match, suppress the warning iff there was a @suppress
// tag, or if the original declaration was a stub.
if (!(allowDupe ||
var.getParentNode().isExprResult()) ||
!newType.isEquivalentTo(varType)) {
report(JSError.make(sourceName, n, DUP_VAR_DECLARATION,
variableName, newType.toString(), var.getInputName(),
String.valueOf(var.nameNode.getLineno()),
varType.toString()));
}
}
}
return newVar;
}
/**
* Expect that all properties on interfaces that this type implements are
* implemented and correctly typed.
*/
void expectAllInterfaceProperties(NodeTraversal t, Node n,
FunctionType type) {
ObjectType instance = type.getInstanceType();
for (ObjectType implemented : type.getAllImplementedInterfaces()) {
if (implemented.getImplicitPrototype() != null) {
for (String prop :
implemented.getImplicitPrototype().getOwnPropertyNames()) {
expectInterfaceProperty(t, n, instance, implemented, prop);
}
}
}
}
/**
* Expect that the property in an interface that this type implements is
* implemented and correctly typed.
*/
private void expectInterfaceProperty(NodeTraversal
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> t, Node n,
ObjectType instance, ObjectType implementedInterface, String prop) {
StaticSlot<JSType> propSlot = instance.getSlot(prop);
if (propSlot == null) {
// Not implemented
String sourceName = n.getSourceFileName();
sourceName = sourceName == null ? "" : sourceName;
registerMismatch(instance, implementedInterface,
report(JSError.make(sourceName, n,
INTERFACE_METHOD_NOT_IMPLEMENTED,
prop, implementedInterface.toString(), instance.toString())));
} else {
Node propNode = propSlot.getDeclaration() == null ?
null : propSlot.getDeclaration().getNode();
// Fall back on the constructor node if we can't find a node for the
// property.
propNode = propNode == null ? n : propNode;
JSType found = propSlot.getType();
found = found.restrictByNotNullOrUndefined();
JSType required
= implementedInterface.getImplicitPrototype().getPropertyType(prop);
TemplateTypeMap typeMap = implementedInterface.getTemplateTypeMap();
if (!typeMap.isEmpty()) {
TemplateTypeMapReplacer replacer = new TemplateTypeMapReplacer(
typeRegistry, typeMap);
required = required.visit(replacer);
}
required = required.restrictByNotNullOrUndefined();
if (!found.isSubtype(required)) {
// Implemented, but not correctly typed
FunctionType constructor =
implementedInterface.toObjectType().getConstructor();
registerMismatch(found, required, report(t.makeError(propNode,
HIDDEN_INTERFACE_PROPERTY_MISMATCH, prop,
constructor.getTopMostDefiningType(prop).toString(),
required.toString(), found.toString())));
}
}
}
/**
* Report a type mismatch
*/
private void mismatch(NodeTraversal t, Node n,
String msg, JSType found, JSType required) {
mismatch(t.getSourceName(), n, msg, found, required);
}
private void mismatch(NodeTraversal t, Node n,
String msg, JSType found, JSTypeNative required) {
mismatch(t, n, msg, found, getNativeType(required));
}
private void mismatch(String sourceName, Node n,
String msg, JSType found, JSType required) {
registerMismatch(found, required, report(
JSError.make(sourceName, n, TYPE_MISMATCH_WARNING,
formatFoundRequired(msg, found, required))));
}
private void registerMismatch(JSType found, JSType required, JSError error) {
// Don't register a mismatch for differences in null or undefined or if the
// code didn't downcast.
found = found.restrictByNotNullOrUndefined();
required = required.restrictByNotNullOrUndefined();
if (found.isSubtype(required) || required.isSubtype(found)) {
return;
}
mismatches.add(new TypeMismatch(found, required, error));
if (found.isFunctionType() &&
required.isFunctionType()) {
FunctionType fnTypeA = found.toMaybeFunctionType();
FunctionType fnTypeB = required.toMaybeFunctionType();
Iterator<Node> paramItA = fnTypeA.getParameters().iterator();
Iterator<Node> paramItB = fnTypeB.getParameters().iterator();
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>
while (paramItA.hasNext() && paramItB.hasNext()) {
registerIfMismatch(paramItA.next().getJSType(),
paramItB.next().getJSType(), error);
}
registerIfMismatch(
fnTypeA.getReturnType(), fnTypeB.getReturnType(), error);
}
}
private void registerIfMismatch(
JSType found, JSType required, JSError error) {
if (found != null && required != null &&
!found.isSubtype(required)) {
registerMismatch(found, required, error);
}
}
/**
* Formats a found/required error message.
*/
private String formatFoundRequired(String description, JSType found,
JSType required) {
return MessageFormat.format(FOUND_REQUIRED, description, found, required);
}
/**
* Given a node, get a human-readable name for the type of that node so
* that will be easy for the programmer to find the original declaration.
*
* For example, if SubFoo's property "bar" might have the human-readable
* name "Foo.prototype.bar".
*
* @param n The node.
* @param dereference If true, the type of the node will be dereferenced
* to an Object type, if possible.
*/
String getReadableJSTypeName(Node n, boolean dereference) {
// If we're analyzing a GETPROP, the property may be inherited by the
// prototype chain. So climb the prototype chain and find out where
// the property was originally defined.
if (n.isGetProp()) {
ObjectType objectType = getJSType(n.getFirstChild()).dereference();
if (objectType != null) {
String propName = n.getLastChild().getString();
if (objectType.getConstructor() != null &&
objectType.getConstructor().isInterface()) {
objectType = FunctionType.getTopDefiningInterface(
objectType, propName);
} else {
// classes
while (objectType != null && !objectType.hasOwnProperty(propName)) {
objectType = objectType.getImplicitPrototype();
}
}
// Don't show complex function names or anonymous types.
// Instead, try to get a human-readable type name.
if (objectType != null &&
(objectType.getConstructor() != null ||
objectType.isFunctionPrototypeType())) {
return objectType.toString() + "." + propName;
}
}
}
JSType type = getJSType(n);
if (dereference) {
ObjectType dereferenced = type.dereference();
if (dereferenced != null) {
type = dereferenced;
}
}
String qualifiedName = n.getQualifiedName();
if (type.isFunctionPrototypeType() ||
(type.toObjectType() != null &&
type.toObjectType().getConstructor() != null)) {
return type.toString();
} else if (qualifiedName != null) {
return qualifiedName;
} else if (type.isFunctionType()) {
// Don't show complex function names.
return "function";
} else {
return type.toString();
}
}
/**
* This method gets the JSType from the Node argument and verifies that
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> {
return null;
}
return p.typeA.getTypesUnderShallowEquality(p.typeB);
}
};
/**
* Merging function for strict non-equality between types.
*/
private static final
Function<TypePair, TypePair> SHNE =
new Function<TypePair, TypePair>() {
@Override
public TypePair apply(TypePair p) {
if (p.typeA == null || p.typeB == null) {
return null;
}
return p.typeA.getTypesUnderShallowInequality(p.typeB);
}
};
/**
* Merging function for inequality comparisons between types.
*/
private final Function<TypePair, TypePair> ineq =
new Function<TypePair, TypePair>() {
@Override
public TypePair apply(TypePair p) {
return new TypePair(
getRestrictedWithoutUndefined(p.typeA),
getRestrictedWithoutUndefined(p.typeB));
}
};
/**
* Creates a semantic reverse abstract interpreter.
*/
public SemanticReverseAbstractInterpreter(CodingConvention convention,
JSTypeRegistry typeRegistry) {
super(convention, typeRegistry);
}
@Override
public FlowScope getPreciserScopeKnowingConditionOutcome(Node condition,
FlowScope blindScope, boolean outcome) {
// Check for the typeof operator.
int operatorToken = condition.getType();
switch (operatorToken) {
case Token.EQ:
case Token.NE:
case Token.SHEQ:
case Token.SHNE:
case Token.CASE:
Node left;
Node right;
if (operatorToken == Token.CASE) {
left = condition.getParent().getFirstChild(); // the switch condition
right = condition.getFirstChild();
} else {
left = condition.getFirstChild();
right = condition.getLastChild();
}
Node typeOfNode = null;
Node stringNode = null;
if (left.isTypeOf() && right.isString()) {
typeOfNode = left;
stringNode = right;
} else if (right.isTypeOf() &&
left.isString()) {
typeOfNode = right;
stringNode = left;
}
if (typeOfNode != null && stringNode != null) {
Node operandNode = typeOfNode.getFirstChild();
JSType operandType = getTypeIfRefinable(operandNode, blindScope);
if (operandType != null) {
boolean resultEqualsValue = operatorToken == Token.EQ ||
operatorToken == Token.SHEQ || operatorToken == Token.CASE;
if (!outcome) {
resultEqualsValue = !resultEqualsValue;
}
return caseTypeOf(operandNode, operandType, stringNode.getString(),
resultEqualsValue, blindScope);
}
}
}
switch (operatorToken) {
case Token.AND:
if (outcome) {
return caseAndOrNotShortCircuiting(condition.getFirstChild(),
condition.getLastChild(), blindScope, true);
} else {
return caseAndOrMaybeShortCircuiting(condition.getFirstChild(),
condition.getLastChild(), blindScope, true);
}
case Token.OR:
if (!outcome) {
return caseAndOrNotShortCircuiting(condition.getFirstChild(),
condition.getLast
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS><JSType> rightVar = rightScope.findUniqueRefinedSlot(blindScope);
if (rightVar == null || !leftVar.getName().equals(rightVar.getName())) {
return blindScope == rightScope ?
blindScope : blindScope.createChildFlowScope();
}
JSType type = leftVar.getType().getLeastSupertype(rightVar.getType());
FlowScope informed = blindScope.createChildFlowScope();
informed.inferSlotType(leftVar.getName(), type);
return informed;
}
/**
* If the restrictedType differs from the originalType, then we should
* branch the current flow scope and create a new flow scope with the name
* declared with the new type.
*
* We try not to create spurious child flow scopes as this makes type
* inference slower.
*
* We also do not want spurious slots around in type inference, because
* we use these as a signal for "checked unknown" types. A "checked unknown"
* type is a symbol that the programmer has already checked and verified that
* it's defined, even if we don't know what it is.
*
* It is OK to pass non-name nodes into this method, as long as you pass
* in {@code null} for a restricted type.
*/
private FlowScope maybeRestrictName(
FlowScope blindScope, Node node, JSType originalType, JSType restrictedType) {
if (restrictedType != null && restrictedType != originalType) {
FlowScope informed = blindScope.createChildFlowScope();
declareNameInScope(informed, node, restrictedType);
return informed;
}
return blindScope;
}
/**
* @see #maybeRestrictName
*/
private FlowScope maybeRestrictTwoNames(
FlowScope blindScope,
Node left, JSType originalLeftType, JSType restrictedLeftType,
Node right, JSType originalRightType, JSType restrictedRightType) {
boolean shouldRefineLeft =
restrictedLeftType != null && restrictedLeftType != originalLeftType;
boolean shouldRefineRight =
restrictedRightType != null && restrictedRightType != originalRightType;
if (shouldRefineLeft || shouldRefineRight) {
FlowScope informed = blindScope.createChildFlowScope();
if (shouldRefineLeft) {
declareNameInScope(informed, left, restrictedLeftType);
}
if (shouldRefineRight) {
declareNameInScope(informed, right, restrictedRightType);
}
return informed;
}
return blindScope;
}
private FlowScope caseNameOrGetProp(Node name, FlowScope blindScope,
boolean outcome) {
JSType type = getTypeIfRefinable(name, blindScope);
if (type != null) {
return maybeRestrictName(
blindScope, name, type,
type.getRestrictedTypeGivenToBooleanOutcome(outcome));
}
return blindScope;
}
private FlowScope caseTypeOf(Node node, JSType type, String value,
boolean resultEqualsValue, FlowScope blindScope) {
return maybeRestrictName(
blindScope, node, type,
getRestrictedByTypeOfResult(type, value, resultEqualsValue));
}
private FlowScope caseInstanceOf
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>(Node left, Node right, FlowScope blindScope,
boolean outcome) {
JSType leftType = getTypeIfRefinable(left, blindScope);
if (leftType == null) {
return blindScope;
}
JSType rightType = right.getJSType();
ObjectType targetType =
typeRegistry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE);
if (rightType != null && rightType.isFunctionType()) {
targetType = rightType.toMaybeFunctionType();
}
Visitor<JSType> visitor;
if (outcome) {
visitor = new RestrictByTrueInstanceOfResultVisitor(targetType);
} else {
visitor = new RestrictByFalseInstanceOfResultVisitor(targetType);
}
return maybeRestrictName(
blindScope, left, leftType, leftType.visit(visitor));
}
/**
* Given 'property in object', ensures that the object has the property in the
* informed scope by defining it as a qualified name if the object type lacks
* the property and it's not in the blind scope.
* @param object The node of the right-side of the in.
* @param propertyName The string of the left-side of the in.
*/
private FlowScope caseIn(Node object, String propertyName, FlowScope blindScope) {
JSType jsType = object.getJSType();
jsType = this.getRestrictedWithoutNull(jsType);
jsType = this.getRestrictedWithoutUndefined(jsType);
boolean hasProperty = false;
ObjectType objectType = ObjectType.cast(jsType);
if (objectType != null) {
hasProperty = objectType.hasProperty(propertyName);
}
if (!hasProperty) {
String qualifiedName = object.getQualifiedName();
if (qualifiedName != null) {
String propertyQualifiedName = qualifiedName + "." + propertyName;
if (blindScope.getSlot(propertyQualifiedName) == null) {
FlowScope informed = blindScope.createChildFlowScope();
JSType unknownType = typeRegistry.getNativeType(
JSTypeNative.UNKNOWN_TYPE);
informed.inferQualifiedSlot(
object, propertyQualifiedName, unknownType, unknownType);
return informed;
}
}
}
return blindScope;
}
/**
* @see SemanticReverseAbstractInterpreter#caseInstanceOf
*/
private class RestrictByTrueInstanceOfResultVisitor
extends RestrictByTrueTypeOfResultVisitor {
private final ObjectType target;
RestrictByTrueInstanceOfResultVisitor(ObjectType target) {
this.target = target;
}
@Override
protected JSType caseTopType(JSType type) {
return applyCommonRestriction(type);
}
@Override
public JSType caseUnknownType() {
FunctionType funcTarget = JSType.toMaybeFunctionType(target);
if (funcTarget != null && funcTarget.hasInstanceType()) {
return funcTarget.getInstanceType();
}
return getNativeType(UNKNOWN_TYPE);
}
@Override
public JSType caseObjectType(ObjectType type) {
return applyCommonRestriction(type);
}
@Override
public JSType caseUnionType(UnionType type) {
return applyCommonRestriction(type);
}
@Override
public JSType caseFunctionType(FunctionType type) {
return caseObjectType(type
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> getStringFromBuffer() {
tokenEnd = cursor;
return new String(stringBuffer, 0, stringBufferTop);
}
private void addToString(int c) {
int n = stringBufferTop;
if (n == stringBuffer.length) {
char[] tmp = new char[stringBuffer.length * 2];
System.arraycopy(stringBuffer, 0, tmp, 0, n);
stringBuffer = tmp;
}
stringBuffer[n] = (char) c;
stringBufferTop = n + 1;
}
void ungetChar(int c) {
// can not unread past across line boundary
assert(!(ungetCursor != 0 && ungetBuffer[ungetCursor - 1] == '\n'));
ungetBuffer[ungetCursor++] = c;
cursor--;
}
private boolean matchChar(int test) {
int c = getCharIgnoreLineEnd();
if (c == test) {
tokenEnd = cursor;
return true;
} else {
ungetCharIgnoreLineEnd(c);
return false;
}
}
private static boolean isAlpha(int c) {
// Use 'Z' < 'a'
if (c <= 'Z') {
return 'A' <= c;
} else {
return 'a' <= c && c <= 'z';
}
}
private boolean isJSDocString(int c) {
switch (c) {
case '@':
case '*':
case ',':
case '>':
case ':':
case '(':
case ')':
case '{':
case '}':
case '[':
case ']':
case '?':
case '!':
case '|':
case '=':
case EOF_CHAR:
case '\n':
return false;
default:
return !isJSSpace(c);
}
}
/* As defined in ECMA. jsscan.c uses C isspace() (which allows
* \v, I think.) note that code in getChar() implicitly accepts
* '\r' == \u000D as well.
*/
static boolean isJSSpace(int c) {
if (c <= 127) {
return c == 0x20 || c == 0x9 || c == 0xC || c == 0xB;
} else {
return c == 0xA0
|| Character.getType((char) c) == Character.SPACE_SEPARATOR;
}
}
private static boolean isJSFormatChar(int c) {
return c > 127 && Character.getType((char) c) == Character.FORMAT;
}
/**
* Allows the JSDocParser to update the character offset
* so that getCharno() returns a valid character position.
*/
void update() {
charno = getOffset();
}
private int peekChar() {
int c = getChar();
ungetChar(c);
return c;
}
protected int getChar() {
if (ungetCursor != 0) {
cursor++;
--ungetCursor;
if (charno == -1) {
charno = getOffset();
}
return ungetBuffer[ungetCursor];
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>FunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING_KEY)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends != null &&
maybeLends.getLendsName() != null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
}
if (parent != null && parent.isAssign()) {
Node lhs = parent.getFirstChild();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
// Only traverse the right side if it's not an assignment to a prototype
// property or subproperty.
if (NodeUtil.isGet(lhs)) {
if (lhs.isGetProp() &&
lhs.getLastChild().getString().equals("prototype")) {
return false;
}
Node llhs = lhs.getFirstChild();
if (llhs.isGetProp() &&
llhs.getLastChild().getString().equals("prototype")) {
return false;
}
}
}
}
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isThis() && shouldReportThis(n)) {
compiler.report(t.makeError(n, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n) {
Node parent = n.getParent();
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
return parent != null && NodeUtil.isGet(parent);
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
* ... x = function() {};
* var ...
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> x = function() {};
* ... var x = function() {};
* </pre>
*/
private JSDocInfo getFunctionJsDocInfo(Node n) {
JSDocInfo jsDoc = n.getJSDocInfo();
Node parent = n.getParent();
if (jsDoc == null) {
int parentType = parent.getType();
if (parentType == Token.NAME || parentType == Token.ASSIGN) {
jsDoc = parent.getJSDocInfo();
if (jsDoc == null && parentType == Token.NAME) {
Node gramps = parent.getParent();
if (gramps.isVar()) {
jsDoc = gramps.getJSDocInfo();
}
}
}
}
return jsDoc;
}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> record.
* @return The record type.
*/
public JSType build() {
// If we have an empty record, simply return the object type.
if (isEmpty) {
return registry.getNativeObjectType(JSTypeNative.OBJECT_TYPE);
}
return new RecordType(
registry, Collections.unmodifiableMap(properties), isDeclared);
}
static class RecordProperty {
private final JSType type;
private final Node propertyNode;
RecordProperty(JSType type, Node propertyNode) {
this.type = type;
this.propertyNode = propertyNode;
}
public JSType getType() {
return type;
}
public Node getPropertyNode() {
return propertyNode;
}
}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>JSC_NON_GLOBAL_DEFINE_INIT_ERROR",
"@define variable {0} assignment must be global");
static final DiagnosticType DEFINE_NOT_ASSIGNABLE_ERROR =
DiagnosticType.error(
"JSC_DEFINE_NOT_ASSIGNABLE_ERROR",
"@define variable {0} cannot be reassigned due to code at {1}.");
private static final MessageFormat REASON_DEFINE_NOT_ASSIGNABLE =
new MessageFormat("line {0} of {1}");
/**
* Create a pass that overrides define constants.
*
* TODO(nicksantos): Write a builder to help JSCompiler induce
* {@code replacements} from command-line flags
*
* @param replacements A hash table of names of defines to their replacements.
* All replacements <b>must</b> be literals.
*/
ProcessDefines(AbstractCompiler compiler, Map<String, Node> replacements) {
this.compiler = compiler;
dominantReplacements = replacements;
}
/**
* Injects a pre-computed global namespace, so that the same namespace
* can be re-used for multiple check passes. Returns {@code this} for
* easy chaining.
*/
ProcessDefines injectNamespace(GlobalNamespace namespace) {
this.namespace = namespace;
return this;
}
@Override
public void process(Node externs, Node root) {
if (namespace == null) {
namespace = new GlobalNamespace(compiler, root);
}
overrideDefines(collectDefines(root, namespace));
}
private void overrideDefines(Map<String, DefineInfo> allDefines) {
boolean changed = false;
for (Map.Entry<String, DefineInfo> def : allDefines.entrySet()) {
String defineName = def.getKey();
DefineInfo info = def.getValue();
Node inputValue = dominantReplacements.get(defineName);
Node finalValue = inputValue != null ?
inputValue : info.getLastValue();
if (finalValue != info.initialValue) {
info.initialValueParent.replaceChild(
info.initialValue, finalValue.cloneTree());
compiler.addToDebugLog("Overriding @define variable " + defineName);
changed = changed ||
finalValue.getType() != info.initialValue.getType() ||
!finalValue.isEquivalentTo(info.initialValue);
}
}
if (changed) {
compiler.reportCodeChange();
}
Set<String> unusedReplacements = dominantReplacements.keySet();
unusedReplacements.removeAll(allDefines.keySet());
unusedReplacements.removeAll(KNOWN_DEFINES);
for (String unknownDefine : unusedReplacements) {
compiler.report(JSError.make(UNKNOWN_DEFINE_WARNING, unknownDefine));
}
}
private static String format(MessageFormat format, Object... params) {
return format.format(params);
}
/**
* Only defines of literal number, string, or boolean are supported.
*/
private boolean isValidDefineType(JSTypeExpression expression) {
JSType type = expression.evaluate(null, compiler.getTypeRegistry());
return !type.isUnknownType() && type.isSubtype(
compiler.getTypeRegistry().getNativeType(
JSTypeNative.NUMBER_STRING_BOOLEAN
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>));
}
/**
* Finds all defines, and creates a {@link DefineInfo} data structure for
* each one.
* @return A map of {@link DefineInfo} structures, keyed by name.
*/
private Map<String, DefineInfo> collectDefines(Node root,
GlobalNamespace namespace) {
// Find all the global names with a @define annotation
List<Name> allDefines = Lists.newArrayList();
for (Name name : namespace.getNameIndex().values()) {
Ref decl = name.getDeclaration();
if (name.docInfo != null && name.docInfo.isDefine()) {
// Process defines should not depend on check types being enabled,
// so we look for the JSDoc instead of the inferred type.
if (isValidDefineType(name.docInfo.getType())) {
allDefines.add(name);
} else {
JSError error = JSError.make(
decl.getSourceName(),
decl.node, INVALID_DEFINE_TYPE_ERROR);
compiler.report(error);
}
} else {
for (Ref ref : name.getRefs()) {
if (ref == decl) {
// Declarations were handled above.
continue;
}
Node n = ref.node;
Node parent = ref.node.getParent();
JSDocInfo info = n.getJSDocInfo();
if (info == null &&
parent.isVar() && parent.hasOneChild()) {
info = parent.getJSDocInfo();
}
if (info != null && info.isDefine()) {
allDefines.add(name);
break;
}
}
}
}
CollectDefines pass = new CollectDefines(compiler, allDefines);
NodeTraversal.traverse(compiler, root, pass);
return pass.getAllDefines();
}
/**
* Finds all assignments to @defines, and figures out the last value of
* the @define.
*/
private static final class CollectDefines implements Callback {
private final AbstractCompiler compiler;
private final Map<String, DefineInfo> assignableDefines;
private final Map<String, DefineInfo> allDefines;
private final Map<Node, RefInfo> allRefInfo;
// A hack that allows us to remove ASSIGN/VAR statements when
// we're currently visiting one of the children of the assign.
private Node lvalueToRemoveLater = null;
// A stack tied to the node traversal, to keep track of whether
// we're in a conditional block. If 1 is at the top, assignment to
// a define is allowed. Otherwise, it's not allowed.
private final Deque<Integer> assignAllowed;
CollectDefines(AbstractCompiler compiler, List<Name> listOfDefines) {
this.compiler = compiler;
this.allDefines = Maps.newHashMap();
assignableDefines = Maps.newHashMap();
assignAllowed = new ArrayDeque<Integer>();
assignAllowed.push(1);
// Create a map of references to defines keyed by node for easy lookup
allRefInfo = Maps.newHashMap();
for (Name name : listOfDefines) {
Ref decl = name.getDeclaration();
if (decl != null) {
allRefInfo.put(decl.node,
new RefInfo(decl, name));
}
for (Ref ref : name
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>(last);
parent.replaceChild(n, last);
} else {
Preconditions.checkState(n.isName());
n.removeChild(n.getFirstChild());
}
compiler.reportCodeChange();
}
if (n.isCall()) {
if (t.inGlobalScope()) {
// If there's a function call in the global scope,
// we just say it's unsafe and freeze all the defines.
//
// NOTE(nicksantos): We could be a lot smarter here. For example,
// ReplaceOverriddenVars keeps a call graph of all functions and
// which functions/variables that they reference, and tries
// to statically determine which functions are "safe" and which
// are not. But this would be overkill, especially because
// the intended use of defines is with config_files, where
// all the defines are at the top of the bundle.
for (DefineInfo info : assignableDefines.values()) {
setDefineInfoNotAssignable(info, t);
}
assignableDefines.clear();
}
}
updateAssignAllowedStack(n, false);
}
/**
* Determines whether assignment to a define should be allowed
* in the subtree of the given node, and if not, records that fact.
*
* @param n The node whose subtree we're about to enter or exit.
* @param entering True if we're entering the subtree, false otherwise.
*/
private void updateAssignAllowedStack(Node n, boolean entering) {
switch (n.getType()) {
case Token.CASE:
case Token.FOR:
case Token.FUNCTION:
case Token.HOOK:
case Token.IF:
case Token.SWITCH:
case Token.WHILE:
if (entering) {
assignAllowed.push(0);
} else {
assignAllowed.remove();
}
break;
}
}
/**
* Determines whether assignment to a define should be allowed
* at the current point of the traversal.
*/
private boolean isAssignAllowed() {
return assignAllowed.element() == 1;
}
/**
* Tracks the given define.
*
* @param t The current traversal, for context.
* @param name The full name for this define.
* @param value The value assigned to the define.
* @param valueParent The parent node of value.
* @return Whether we should remove this assignment from the parse tree.
*/
private boolean processDefineAssignment(NodeTraversal t,
String name, Node value, Node valueParent) {
if (value == null || !NodeUtil.isValidDefineValue(value,
allDefines.keySet())) {
compiler.report(
t.makeError(value, INVALID_DEFINE_INIT_ERROR, name));
} else if (!isAssignAllowed()) {
compiler.report(
t.makeError(valueParent, NON_GLOBAL_DEFINE_INIT_ERROR, name));
} else {
DefineInfo info = allDefines.get(name);
if (info == null) {
// First declaration of this define.
info = new DefineInfo(value, valueParent);
allDefines.put(name, info);
assignableDefines.put(name, info);
} else if (info.recordAssignment(value)) {
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>.javascript.rhino.jstype.TemplateType;
import com.google.javascript.rhino.jstype.TemplateTypeMap;
import com.google.javascript.rhino.jstype.TemplateTypeMapReplacer;
import com.google.javascript.rhino.jstype.UnionType;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* Type inference within a script node or a function body, using the data-flow
* analysis framework.
*
*/
class TypeInference
extends DataFlowAnalysis.BranchedForwardDataFlowAnalysis<Node, FlowScope> {
// TODO(johnlenz): We no longer make this check, but we should.
static final DiagnosticType FUNCTION_LITERAL_UNDEFINED_THIS =
DiagnosticType.warning(
"JSC_FUNCTION_LITERAL_UNDEFINED_THIS",
"Function literal argument refers to undefined this argument");
private final AbstractCompiler compiler;
private final JSTypeRegistry registry;
private final ReverseAbstractInterpreter reverseInterpreter;
private final Scope syntacticScope;
private final FlowScope functionScope;
private final FlowScope bottomScope;
private final Map<String, AssertionFunctionSpec> assertionFunctionsMap;
// For convenience
private final ObjectType unknownType;
TypeInference(AbstractCompiler compiler, ControlFlowGraph<Node> cfg,
ReverseAbstractInterpreter reverseInterpreter,
Scope functionScope,
Map<String, AssertionFunctionSpec> assertionFunctionsMap) {
super(cfg, new LinkedFlowScope.FlowScopeJoinOp());
this.compiler = compiler;
this.registry = compiler.getTypeRegistry();
this.reverseInterpreter = reverseInterpreter;
this.unknownType = registry.getNativeObjectType(UNKNOWN_TYPE);
this.syntacticScope = functionScope;
inferArguments(functionScope);
this.functionScope = LinkedFlowScope.createEntryLattice(functionScope);
this.assertionFunctionsMap = assertionFunctionsMap;
// For each local variable declared with the VAR keyword, the entry
// type is VOID.
Iterator<Var> varIt =
functionScope.getDeclarativelyUnboundVarsWithoutTypes();
while (varIt.hasNext()) {
Var var = varIt.next();
if (isUnflowable(var)) {
continue;
}
this.functionScope.inferSlotType(
var.getName(), getNativeType(VOID_TYPE));
}
this.bottomScope = LinkedFlowScope.createEntryLattice(
Scope.createLatticeBottom(functionScope.getRootNode()));
}
/**
* Infers all of a function's arguments if their types aren't declared.
*/
private void inferArguments(Scope functionScope) {
Node functionNode = functionScope.getRootNode();
Node astParameters = functionNode.getFirstChild().getNext();
Node iifeArgumentNode = null;
if (NodeUtil.isCallOrNewTarget(functionNode)) {
iifeArgumentNode = functionNode.getNext();
}
FunctionType functionType =
JSType.toMaybeFunctionType(functionNode.getJSType());
if (functionType != null) {
Node parameterTypes = functionType.getParametersNode();
if (parameterTypes != null) {
Node parameterTypeNode = parameterTypes.getFirstChild();
for (
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>Node astParameter : astParameters.children()) {
Var var = functionScope.getVar(astParameter.getString());
Preconditions.checkNotNull(var);
if (var.isTypeInferred() &&
var.getType() == unknownType) {
JSType newType = null;
if (iifeArgumentNode != null) {
newType = iifeArgumentNode.getJSType();
} else if (parameterTypeNode != null) {
newType = parameterTypeNode.getJSType();
}
if (newType != null) {
var.setType(newType);
astParameter.setJSType(newType);
}
}
if (parameterTypeNode != null) {
parameterTypeNode = parameterTypeNode.getNext();
}
if (iifeArgumentNode != null) {
iifeArgumentNode = iifeArgumentNode.getNext();
}
}
}
}
}
@Override
FlowScope createInitialEstimateLattice() {
return bottomScope;
}
@Override
FlowScope createEntryLattice() {
return functionScope;
}
@Override
FlowScope flowThrough(Node n, FlowScope input) {
// If we have not walked a path from <entry> to <n>, then we don't
// want to infer anything about this scope.
if (input == bottomScope) {
return input;
}
FlowScope output = input.createChildFlowScope();
output = traverse(n, output);
return output;
}
@Override
@SuppressWarnings({"fallthrough", "incomplete-switch"})
List<FlowScope> branchedFlowThrough(Node source, FlowScope input) {
// NOTE(nicksantos): Right now, we just treat ON_EX edges like UNCOND
// edges. If we wanted to be perfect, we'd actually JOIN all the out
// lattices of this flow with the in lattice, and then make that the out
// lattice for the ON_EX edge. But it's probably too expensive to be
// worthwhile.
FlowScope output = flowThrough(source, input);
Node condition = null;
FlowScope conditionFlowScope = null;
BooleanOutcomePair conditionOutcomes = null;
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(source);
List<FlowScope> result = Lists.newArrayListWithCapacity(branchEdges.size());
for (DiGraphEdge<Node, Branch> branchEdge : branchEdges) {
Branch branch = branchEdge.getValue();
FlowScope newScope = output;
switch (branch) {
case ON_TRUE:
if (NodeUtil.isForIn(source)) {
// item is assigned a property name, so its type should be string.
Node item = source.getFirstChild();
Node obj = item.getNext();
FlowScope informed = traverse(obj, output.createChildFlowScope());
if (item.isVar()) {
item = item.getFirstChild();
}
if (item.isName()) {
JSType iterKeyType = getNativeType(STRING_TYPE);
ObjectType objType = getJSType(obj).dereference();
JSType objIndexType = objType == null ?
null : objType.getTemplateTypeMap().getTemplateType(
registry.getObjectIndex
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>Key());
if (objIndexType != null && !objIndexType.isUnknownType()) {
JSType narrowedKeyType =
iterKeyType.getGreatestSubtype(objIndexType);
if (!narrowedKeyType.isEmptyType()) {
iterKeyType = narrowedKeyType;
}
}
redeclareSimpleVar(informed, item, iterKeyType);
}
newScope = informed;
break;
}
// FALL THROUGH
case ON_FALSE:
if (condition == null) {
condition = NodeUtil.getConditionExpression(source);
if (condition == null && source.isCase()) {
condition = source;
// conditionFlowScope is cached from previous iterations
// of the loop.
if (conditionFlowScope == null) {
conditionFlowScope = traverse(
condition.getFirstChild(), output.createChildFlowScope());
}
}
}
if (condition != null) {
if (condition.isAnd() ||
condition.isOr()) {
// When handling the short-circuiting binary operators,
// the outcome scope on true can be different than the outcome
// scope on false.
//
// TODO(nicksantos): The "right" way to do this is to
// carry the known outcome all the way through the
// recursive traversal, so that we can construct a
// different flow scope based on the outcome. However,
// this would require a bunch of code and a bunch of
// extra computation for an edge case. This seems to be
// a "good enough" approximation.
// conditionOutcomes is cached from previous iterations
// of the loop.
if (conditionOutcomes == null) {
conditionOutcomes = condition.isAnd() ?
traverseAnd(condition, output.createChildFlowScope()) :
traverseOr(condition, output.createChildFlowScope());
}
newScope =
reverseInterpreter.getPreciserScopeKnowingConditionOutcome(
condition,
conditionOutcomes.getOutcomeFlowScope(
condition.getType(), branch == Branch.ON_TRUE),
branch == Branch.ON_TRUE);
} else {
// conditionFlowScope is cached from previous iterations
// of the loop.
if (conditionFlowScope == null) {
conditionFlowScope =
traverse(condition, output.createChildFlowScope());
}
newScope =
reverseInterpreter.getPreciserScopeKnowingConditionOutcome(
condition, conditionFlowScope, branch == Branch.ON_TRUE);
}
}
break;
}
result.add(newScope.optimize());
}
return result;
}
private FlowScope traverse(Node n, FlowScope scope) {
switch (n.getType()) {
case Token.ASSIGN:
scope = traverseAssign(n, scope);
break;
case Token.NAME:
scope = traverseName(n, scope);
break;
case Token.GETPROP:
scope = traverseGetProp(n, scope);
break;
case Token.AND:
scope = traverseAnd(n, scope).getJoinedFlowScope()
.createChildFlowScope();
break;
case Token.OR:
scope = traverseOr(n, scope).getJoinedFlowScope()
.createChildFlowScope();
break;
case Token.HOOK:
scope = traverseHook(n
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>, scope);
break;
case Token.OBJECTLIT:
scope = traverseObjectLiteral(n, scope);
break;
case Token.CALL:
scope = traverseCall(n, scope);
break;
case Token.NEW:
scope = traverseNew(n, scope);
break;
case Token.ASSIGN_ADD:
case Token.ADD:
scope = traverseAdd(n, scope);
break;
case Token.POS:
case Token.NEG:
scope = traverse(n.getFirstChild(), scope); // Find types.
n.setJSType(getNativeType(NUMBER_TYPE));
break;
case Token.ARRAYLIT:
scope = traverseArrayLiteral(n, scope);
break;
case Token.THIS:
n.setJSType(scope.getTypeOfThis());
break;
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.LSH:
case Token.RSH:
case Token.ASSIGN_URSH:
case Token.URSH:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_MUL:
case Token.ASSIGN_SUB:
case Token.DIV:
case Token.MOD:
case Token.BITAND:
case Token.BITXOR:
case Token.BITOR:
case Token.MUL:
case Token.SUB:
case Token.DEC:
case Token.INC:
case Token.BITNOT:
scope = traverseChildren(n, scope);
n.setJSType(getNativeType(NUMBER_TYPE));
break;
case Token.PARAM_LIST:
scope = traverse(n.getFirstChild(), scope);
n.setJSType(getJSType(n.getFirstChild()));
break;
case Token.COMMA:
scope = traverseChildren(n, scope);
n.setJSType(getJSType(n.getLastChild()));
break;
case Token.TYPEOF:
scope = traverseChildren(n, scope);
n.setJSType(getNativeType(STRING_TYPE));
break;
case Token.DELPROP:
case Token.LT:
case Token.LE:
case Token.GT:
case Token.GE:
case Token.NOT:
case Token.EQ:
case Token.NE:
case Token.SHEQ:
case Token.SHNE:
case Token.INSTANCEOF:
case Token.IN:
scope = traverseChildren(n, scope);
n.setJSType(getNativeType(BOOLEAN_TYPE));
break;
case Token.GETELEM:
scope = traverseGetElem(n, scope);
break;
case Token.EXPR_RESULT:
scope = traverseChildren(n, scope);
if (n.getFirstChild().isGetProp()) {
ensurePropertyDeclared(n.getFirstChild());
}
break;
case Token.SWITCH:
scope = traverse(n.getFirstChild(), scope);
break;
case Token.RETURN:
scope = traverseReturn(n, scope);
break;
case Token.VAR:
case Token.THROW:
scope = traverseChildren(n, scope);
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>
break;
case Token.CATCH:
scope = traverseCatch(n, scope);
break;
case Token.CAST:
scope = traverseChildren(n, scope);
JSDocInfo info = n.getJSDocInfo();
if (info != null && info.hasType()) {
n.setJSType(info.getType().evaluate(syntacticScope, registry));
}
break;
}
return scope;
}
/**
* Traverse a return value.
*/
private FlowScope traverseReturn(Node n, FlowScope scope) {
scope = traverseChildren(n, scope);
Node retValue = n.getFirstChild();
if (retValue != null) {
JSType type = functionScope.getRootNode().getJSType();
if (type != null) {
FunctionType fnType = type.toMaybeFunctionType();
if (fnType != null) {
inferPropertyTypesToMatchConstraint(
retValue.getJSType(), fnType.getReturnType());
}
}
}
return scope;
}
/**
* Any value can be thrown, so it's really impossible to determine the type
* of a CATCH param. Treat it as the UNKNOWN type.
*/
private FlowScope traverseCatch(Node catchNode, FlowScope scope) {
Node name = catchNode.getFirstChild();
JSType type;
// If the catch expression name was declared in the catch use that type,
// otherwise use "unknown".
JSDocInfo info = name.getJSDocInfo();
if (info != null && info.hasType()) {
type = info.getType().evaluate(syntacticScope, registry);
} else {
type = getNativeType(JSTypeNative.UNKNOWN_TYPE);
}
redeclareSimpleVar(scope, name, type);
name.setJSType(type);
return scope;
}
private FlowScope traverseAssign(Node n, FlowScope scope) {
Node left = n.getFirstChild();
Node right = n.getLastChild();
scope = traverseChildren(n, scope);
JSType leftType = left.getJSType();
JSType rightType = getJSType(right);
n.setJSType(rightType);
updateScopeForTypeChange(scope, left, leftType, rightType);
return scope;
}
/**
* Updates the scope according to the result of a type change, like
* an assignment or a type cast.
*/
private void updateScopeForTypeChange(
FlowScope scope, Node left, JSType leftType, JSType resultType) {
Preconditions.checkNotNull(resultType);
switch (left.getType()) {
case Token.NAME:
String varName = left.getString();
Var var = syntacticScope.getVar(varName);
// When looking at VAR initializers for declared VARs, we trust
// the declared type over the type it's being initialized to.
// This has two purposes:
// 1) We avoid re-declaring declared variables so that built-in
// types defined in externs are not redeclared.
// 2) When there's a lexical closure like
// /** @type {?string} */ var x = null;
// function f() { x = 'xyz'; }
// the inference
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> will ignore the lexical closure,
// which is just wrong. This bug needs to be fixed eventually.
boolean isVarDeclaration = left.hasChildren();
if (!isVarDeclaration || var == null || var.isTypeInferred()) {
redeclareSimpleVar(scope, left, resultType);
}
left.setJSType(isVarDeclaration || leftType == null ?
resultType : null);
if (var != null && var.isTypeInferred()) {
JSType oldType = var.getType();
var.setType(oldType == null ?
resultType : oldType.getLeastSupertype(resultType));
}
break;
case Token.GETPROP:
String qualifiedName = left.getQualifiedName();
if (qualifiedName != null) {
scope.inferQualifiedSlot(left, qualifiedName,
leftType == null ? unknownType : leftType,
resultType);
}
left.setJSType(resultType);
ensurePropertyDefined(left, resultType);
break;
}
}
/**
* Defines a property if the property has not been defined yet.
*/
private void ensurePropertyDefined(Node getprop, JSType rightType) {
String propName = getprop.getLastChild().getString();
Node obj = getprop.getFirstChild();
JSType nodeType = getJSType(obj);
ObjectType objectType = ObjectType.cast(
nodeType.restrictByNotNullOrUndefined());
if (objectType == null) {
registry.registerPropertyOnType(propName, nodeType);
} else {
// Don't add the property to @struct objects outside a constructor
if (nodeType.isStruct() && !objectType.hasProperty(propName)) {
if (!(obj.isThis() &&
getJSType(syntacticScope.getRootNode()).isConstructor())) {
return;
}
}
if (ensurePropertyDeclaredHelper(getprop, objectType)) {
return;
}
if (!objectType.isPropertyTypeDeclared(propName)) {
// We do not want a "stray" assign to define an inferred property
// for every object of this type in the program. So we use a heuristic
// approach to determine whether to infer the property.
//
// 1) If the property is already defined, join it with the previously
// inferred type.
// 2) If this isn't an instance object, define it.
// 3) If the property of an object is being assigned in the constructor,
// define it.
// 4) If this is a stub, define it.
// 5) Otherwise, do not define the type, but declare it in the registry
// so that we can use it for missing property checks.
if (objectType.hasProperty(propName) || !objectType.isInstanceType()) {
if ("prototype".equals(propName)) {
objectType.defineDeclaredProperty(propName, rightType, getprop);
} else {
objectType.defineInferredProperty(propName, rightType, getprop);
}
} else if (obj.isThis() &&
getJSType(syntacticScope.getRootNode()).isConstructor()) {
objectType.defineInferredProperty(propName, rightType, getprop);
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> else {
registry.registerPropertyOnType(propName, objectType);
}
}
}
}
/**
* Defines a declared property if it has not been defined yet.
*
* This handles the case where a property is declared on an object where
* the object type is inferred, and so the object type will not
* be known in {@code TypedScopeCreator}.
*/
private void ensurePropertyDeclared(Node getprop) {
ObjectType ownerType = ObjectType.cast(
getJSType(getprop.getFirstChild()).restrictByNotNullOrUndefined());
if (ownerType != null) {
ensurePropertyDeclaredHelper(getprop, ownerType);
}
}
/**
* Declares a property on its owner, if necessary.
* @return True if a property was declared.
*/
private boolean ensurePropertyDeclaredHelper(
Node getprop, ObjectType objectType) {
String propName = getprop.getLastChild().getString();
String qName = getprop.getQualifiedName();
if (qName != null) {
Var var = syntacticScope.getVar(qName);
if (var != null && !var.isTypeInferred()) {
// Handle normal declarations that could not be addressed earlier.
if (propName.equals("prototype") ||
// Handle prototype declarations that could not be addressed earlier.
(!objectType.hasOwnProperty(propName) &&
(!objectType.isInstanceType() ||
(var.isExtern() && !objectType.isNativeObjectType())))) {
return objectType.defineDeclaredProperty(
propName, var.getType(), getprop);
}
}
}
return false;
}
private FlowScope traverseName(Node n, FlowScope scope) {
String varName = n.getString();
Node value = n.getFirstChild();
JSType type = n.getJSType();
if (value != null) {
scope = traverse(value, scope);
updateScopeForTypeChange(scope, n, n.getJSType() /* could be null */,
getJSType(value));
return scope;
} else {
StaticSlot<JSType> var = scope.getSlot(varName);
if (var != null) {
// There are two situations where we don't want to use type information
// from the scope, even if we have it.
// 1) The var is escaped and assigned in an inner scope, e.g.,
// function f() { var x = 3; function g() { x = null } (x); }
boolean isInferred = var.isTypeInferred();
boolean unflowable = isInferred &&
isUnflowable(syntacticScope.getVar(varName));
// 2) We're reading type information from another scope for an
// inferred variable. That variable is assigned more than once,
// and we can't know which type we're getting.
//
// var t = null; function f() { (t); } doStuff(); t = {};
//
// Notice that this heuristic isn't perfect. For example, you might
// have:
//
// function f() { (t); } f(); var t = 3;
//
// In this case, we would infer the first reference to t as
// type {number}, even though it
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>'s undefined.
boolean nonLocalInferredSlot = false;
if (isInferred && syntacticScope.isLocal()) {
Var maybeOuterVar = syntacticScope.getParent().getVar(varName);
if (var == maybeOuterVar &&
!maybeOuterVar.isMarkedAssignedExactlyOnce()) {
nonLocalInferredSlot = true;
}
}
if (!unflowable && !nonLocalInferredSlot) {
type = var.getType();
if (type == null) {
type = unknownType;
}
}
}
}
n.setJSType(type);
return scope;
}
/** Traverse each element of the array. */
private FlowScope traverseArrayLiteral(Node n, FlowScope scope) {
scope = traverseChildren(n, scope);
n.setJSType(getNativeType(ARRAY_TYPE));
return scope;
}
private FlowScope traverseObjectLiteral(Node n, FlowScope scope) {
JSType type = n.getJSType();
Preconditions.checkNotNull(type);
for (Node name = n.getFirstChild(); name != null; name = name.getNext()) {
scope = traverse(name.getFirstChild(), scope);
}
// Object literals can be reflected on other types.
// See CodingConvention#getObjectLiteralCase and goog.object.reflect.
// Ignore these types of literals.
ObjectType objectType = ObjectType.cast(type);
if (objectType == null
|| n.getBooleanProp(Node.REFLECTED_OBJECT)
|| objectType.isEnumType()) {
return scope;
}
String qObjName = NodeUtil.getBestLValueName(
NodeUtil.getBestLValue(n));
for (Node name = n.getFirstChild(); name != null;
name = name.getNext()) {
String memberName = NodeUtil.getObjectLitKeyName(name);
if (memberName != null) {
JSType rawValueType = name.getFirstChild().getJSType();
JSType valueType = NodeUtil.getObjectLitKeyTypeFromValueType(
name, rawValueType);
if (valueType == null) {
valueType = unknownType;
}
objectType.defineInferredProperty(memberName, valueType, name);
// Do normal flow inference if this is a direct property assignment.
if (qObjName != null && name.isStringKey()) {
String qKeyName = qObjName + "." + memberName;
Var var = syntacticScope.getVar(qKeyName);
JSType oldType = var == null ? null : var.getType();
if (var != null && var.isTypeInferred()) {
var.setType(oldType == null ?
valueType : oldType.getLeastSupertype(oldType));
}
scope.inferQualifiedSlot(name, qKeyName,
oldType == null ? unknownType : oldType,
valueType);
}
} else {
n.setJSType(unknownType);
}
}
return scope;
}
private FlowScope traverseAdd(Node n, FlowScope scope) {
Node left = n.getFirstChild();
Node right = left.getNext();
scope = traverseChildren(n, scope);
JSType leftType = left.getJSType();
JSType rightType = right.get
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>JSType();
JSType type = unknownType;
if (leftType != null && rightType != null) {
boolean leftIsUnknown = leftType.isUnknownType();
boolean rightIsUnknown = rightType.isUnknownType();
if (leftIsUnknown && rightIsUnknown) {
type = unknownType;
} else if ((!leftIsUnknown && leftType.isString()) ||
(!rightIsUnknown && rightType.isString())) {
type = getNativeType(STRING_TYPE);
} else if (leftIsUnknown || rightIsUnknown) {
type = unknownType;
} else if (isAddedAsNumber(leftType) && isAddedAsNumber(rightType)) {
type = getNativeType(NUMBER_TYPE);
} else {
type = registry.createUnionType(STRING_TYPE, NUMBER_TYPE);
}
}
n.setJSType(type);
if (n.isAssignAdd()) {
updateScopeForTypeChange(scope, left, leftType, type);
}
return scope;
}
private boolean isAddedAsNumber(JSType type) {
return type.isSubtype(registry.createUnionType(VOID_TYPE, NULL_TYPE,
NUMBER_VALUE_OR_OBJECT_TYPE, BOOLEAN_TYPE, BOOLEAN_OBJECT_TYPE));
}
private FlowScope traverseHook(Node n, FlowScope scope) {
Node condition = n.getFirstChild();
Node trueNode = condition.getNext();
Node falseNode = n.getLastChild();
// verify the condition
scope = traverse(condition, scope);
// reverse abstract interpret the condition to produce two new scopes
FlowScope trueScope = reverseInterpreter.
getPreciserScopeKnowingConditionOutcome(
condition, scope, true);
FlowScope falseScope = reverseInterpreter.
getPreciserScopeKnowingConditionOutcome(
condition, scope, false);
// traverse the true node with the trueScope
traverse(trueNode, trueScope.createChildFlowScope());
// traverse the false node with the falseScope
traverse(falseNode, falseScope.createChildFlowScope());
// meet true and false nodes' types and assign
JSType trueType = trueNode.getJSType();
JSType falseType = falseNode.getJSType();
if (trueType != null && falseType != null) {
n.setJSType(trueType.getLeastSupertype(falseType));
} else {
n.setJSType(null);
}
return scope.createChildFlowScope();
}
private FlowScope traverseCall(Node n, FlowScope scope) {
scope = traverseChildren(n, scope);
Node left = n.getFirstChild();
JSType functionType = getJSType(left).restrictByNotNullOrUndefined();
if (functionType.isFunctionType()) {
FunctionType fnType = functionType.toMaybeFunctionType();
n.setJSType(fnType.getReturnType());
backwardsInferenceFromCallSite(n, fnType);
} else if (functionType.isEquivalentTo(
getNativeType(CHECKED_UNKNOWN_TYPE))) {
n.setJSType(getNativeType(CHECKED_UNKNOWN_TYPE));
}
scope = tightenTypesAfterAssertions(scope, n);
return scope;
}
private FlowScope tightenTypesAfter
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>Assertions(FlowScope scope,
Node callNode) {
Node left = callNode.getFirstChild();
Node firstParam = left.getNext();
AssertionFunctionSpec assertionFunctionSpec =
assertionFunctionsMap.get(left.getQualifiedName());
if (assertionFunctionSpec == null || firstParam == null) {
return scope;
}
Node assertedNode = assertionFunctionSpec.getAssertedParam(firstParam);
if (assertedNode == null) {
return scope;
}
JSType assertedType = assertionFunctionSpec.getAssertedType(
callNode, registry);
String assertedNodeName = assertedNode.getQualifiedName();
JSType narrowed;
// Handle assertions that enforce expressions evaluate to true.
if (assertedType == null) {
// Handle arbitrary expressions within the assert.
scope = reverseInterpreter.getPreciserScopeKnowingConditionOutcome(
assertedNode, scope, true);
// Build the result of the assertExpression
narrowed = getJSType(assertedNode).restrictByNotNullOrUndefined();
} else {
// Handle assertions that enforce expressions are of a certain type.
JSType type = getJSType(assertedNode);
narrowed = type.getGreatestSubtype(assertedType);
if (assertedNodeName != null && type.differsFrom(narrowed)) {
scope = narrowScope(scope, assertedNode, narrowed);
}
}
callNode.setJSType(narrowed);
return scope;
}
private FlowScope narrowScope(FlowScope scope, Node node, JSType narrowed) {
if (node.isThis()) {
// "this" references don't need to be modeled in the control flow graph.
return scope;
}
scope = scope.createChildFlowScope();
if (node.isGetProp()) {
scope.inferQualifiedSlot(
node, node.getQualifiedName(), getJSType(node), narrowed);
} else {
redeclareSimpleVar(scope, node, narrowed);
}
return scope;
}
/**
* We only do forward type inference. We do not do full backwards
* type inference.
*
* In other words, if we have,
* <code>
* var x = f();
* g(x);
* </code>
* a forward type-inference engine would try to figure out the type
* of "x" from the return type of "f". A backwards type-inference engine
* would try to figure out the type of "x" from the parameter type of "g".
*
* However, there are a few special syntactic forms where we do some
* some half-assed backwards type-inference, because programmers
* expect it in this day and age. To take an example from Java,
* <code>
* List<String> x = Lists.newArrayList();
* </code>
* The Java compiler will be able to infer the generic type of the List
* returned by newArrayList().
*
* In much the same way, we do some special-case backwards inference for
* JS. Those cases are enumerated here.
*/
private void backwardsInferenceFromCallSite(Node n, FunctionType fnType) {
boolean updatedFnType = inferTemplatedTypesForCall(n,
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>);
if (!resolved.isUnknownType()) {
if (previous == null) {
map.put(template, resolved);
} else {
JSType join = previous.getLeastSupertype(resolved);
map.put(template, join);
}
}
}
private static class TemplateTypeReplacer extends ModificationVisitor {
private final Map<TemplateType, JSType> replacements;
private final JSTypeRegistry registry;
boolean madeChanges = false;
TemplateTypeReplacer(
JSTypeRegistry registry, Map<TemplateType, JSType> replacements) {
super(registry);
this.registry = registry;
this.replacements = replacements;
}
@Override
public JSType caseTemplateType(TemplateType type) {
madeChanges = true;
JSType replacement = replacements.get(type);
return replacement != null ?
replacement : registry.getNativeType(UNKNOWN_TYPE);
}
}
/**
* For functions with function(this: T, ...) and T as parameters, type
* inference will set the type of this on a function literal argument to the
* the actual type of T.
*/
private boolean inferTemplatedTypesForCall(
Node n, FunctionType fnType) {
if (fnType.getTemplateTypeMap().getTemplateKeys().isEmpty()) {
return false;
}
// Try to infer the template types
Map<TemplateType, JSType> inferred = inferTemplateTypesFromParameters(
fnType, n);
// Replace all template types. If we couldn't find a replacement, we
// replace it with UNKNOWN.
TemplateTypeReplacer replacer = new TemplateTypeReplacer(
registry, inferred);
Node callTarget = n.getFirstChild();
FunctionType replacementFnType = fnType.visit(replacer)
.toMaybeFunctionType();
Preconditions.checkNotNull(replacementFnType);
callTarget.setJSType(replacementFnType);
n.setJSType(replacementFnType.getReturnType());
return replacer.madeChanges;
}
private FlowScope traverseNew(Node n, FlowScope scope) {
scope = traverseChildren(n, scope);
Node constructor = n.getFirstChild();
JSType constructorType = constructor.getJSType();
JSType type = null;
if (constructorType != null) {
constructorType = constructorType.restrictByNotNullOrUndefined();
if (constructorType.isUnknownType()) {
type = unknownType;
} else {
FunctionType ct = constructorType.toMaybeFunctionType();
if (ct == null && constructorType instanceof FunctionType) {
// If constructorType is a NoObjectType, then toMaybeFunctionType will
// return null. But NoObjectType implements the FunctionType
// interface, precisely because it can validly construct objects.
ct = (FunctionType) constructorType;
}
if (ct != null && ct.isConstructor()) {
backwardsInferenceFromCallSite(n, ct);
// If necessary, create a TemplatizedType wrapper around the instance
// type, based on the types of the constructor parameters.
ObjectType instanceType = ct.getInstanceType();
Map<TemplateType, JSType> inferredTypes =
inferTemplateTypesFromParameters(ct, n);
if (inferredTypes.isEmpty()) {
type = instanceType;
} else {
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>
type = registry.createTemplatizedType(instanceType, inferredTypes);
}
}
}
}
n.setJSType(type);
return scope;
}
private BooleanOutcomePair traverseAnd(Node n, FlowScope scope) {
return traverseShortCircuitingBinOp(n, scope, true);
}
private FlowScope traverseChildren(Node n, FlowScope scope) {
for (Node el = n.getFirstChild(); el != null; el = el.getNext()) {
scope = traverse(el, scope);
}
return scope;
}
private FlowScope traverseGetElem(Node n, FlowScope scope) {
scope = traverseChildren(n, scope);
JSType type = getJSType(n.getFirstChild()).restrictByNotNullOrUndefined();
TemplateTypeMap typeMap = type.getTemplateTypeMap();
if (typeMap.hasTemplateType(registry.getObjectElementKey())) {
n.setJSType(typeMap.getTemplateType(registry.getObjectElementKey()));
}
return dereferencePointer(n.getFirstChild(), scope);
}
private FlowScope traverseGetProp(Node n, FlowScope scope) {
Node objNode = n.getFirstChild();
Node property = n.getLastChild();
scope = traverseChildren(n, scope);
n.setJSType(
getPropertyType(
objNode.getJSType(), property.getString(), n, scope));
return dereferencePointer(n.getFirstChild(), scope);
}
/**
* Suppose X is an object with inferred properties.
* Suppose also that X is used in a way where it would only type-check
* correctly if some of those properties are widened.
* Then we should be polite and automatically widen X's properties for him.
*
* For a concrete example, consider:
* param x {{prop: (number|undefined)}}
* function f(x) {}
* f({});
*
* If we give the anonymous object an inferred property of (number|undefined),
* then this code will type-check appropriately.
*/
private static void inferPropertyTypesToMatchConstraint(
JSType type, JSType constraint) {
if (type == null || constraint == null) {
return;
}
type.matchConstraint(constraint);
}
/**
* If we access a property of a symbol, then that symbol is not
* null or undefined.
*/
private FlowScope dereferencePointer(Node n, FlowScope scope) {
if (n.isQualifiedName()) {
JSType type = getJSType(n);
JSType narrowed = type.restrictByNotNullOrUndefined();
if (type != narrowed) {
scope = narrowScope(scope, n, narrowed);
}
}
return scope;
}
private JSType getPropertyType(JSType objType, String propName,
Node n, FlowScope scope) {
// We often have a couple of different types to choose from for the
// property. Ordered by accuracy, we have
// 1) A locally inferred qualified name (which is in the FlowScope)
// 2) A globally declared qualified name (which is in the FlowScope)
// 3) A property on the owner type (which is on objType)
// 4) A
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> name in the type registry (as a last resort)
JSType propertyType = null;
boolean isLocallyInferred = false;
// Scopes sometimes contain inferred type info about qualified names.
String qualifiedName = n.getQualifiedName();
StaticSlot<JSType> var = scope.getSlot(qualifiedName);
if (var != null) {
JSType varType = var.getType();
if (varType != null) {
boolean isDeclared = !var.isTypeInferred();
isLocallyInferred = (var != syntacticScope.getSlot(qualifiedName));
if (isDeclared || isLocallyInferred) {
propertyType = varType;
}
}
}
if (propertyType == null && objType != null) {
JSType foundType = objType.findPropertyType(propName);
if (foundType != null) {
propertyType = foundType;
}
}
if (propertyType != null && objType != null) {
JSType restrictedObjType = objType.restrictByNotNullOrUndefined();
if (restrictedObjType.isTemplatizedType()
&& propertyType.hasAnyTemplateTypes()) {
TemplateTypeMap typeMap = restrictedObjType.getTemplateTypeMap();
TemplateTypeMapReplacer replacer = new TemplateTypeMapReplacer(
registry, typeMap);
propertyType = propertyType.visit(replacer);
}
}
if ((propertyType == null || propertyType.isUnknownType())
&& qualifiedName != null) {
// If we find this node in the registry, then we can infer its type.
ObjectType regType = ObjectType.cast(registry.getType(qualifiedName));
if (regType != null) {
propertyType = regType.getConstructor();
}
}
if (propertyType == null) {
return unknownType;
} else if (propertyType.isEquivalentTo(unknownType) && isLocallyInferred) {
// If the type has been checked in this scope,
// then use CHECKED_UNKNOWN_TYPE instead to indicate that.
return getNativeType(CHECKED_UNKNOWN_TYPE);
} else {
return propertyType;
}
}
private BooleanOutcomePair traverseOr(Node n, FlowScope scope) {
return traverseShortCircuitingBinOp(n, scope, false);
}
private BooleanOutcomePair traverseShortCircuitingBinOp(
Node n, FlowScope scope, boolean condition) {
Node left = n.getFirstChild();
Node right = n.getLastChild();
// type the left node
BooleanOutcomePair leftLiterals =
traverseWithinShortCircuitingBinOp(left,
scope.createChildFlowScope());
JSType leftType = left.getJSType();
// reverse abstract interpret the left node to produce the correct
// scope in which to verify the right node
FlowScope rightScope = reverseInterpreter.
getPreciserScopeKnowingConditionOutcome(
left, leftLiterals.getOutcomeFlowScope(left.getType(), condition),
condition);
// type the right node
BooleanOutcomePair rightLiterals =
traverseWithinShortCircuitingBinOp(
right, rightScope.createChildFlowScope());
JSType rightType = right.getJSType();
JSType type;
BooleanOutcomePair literals;
if (leftType != null
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> && rightType != null) {
leftType = leftType.getRestrictedTypeGivenToBooleanOutcome(!condition);
if (leftLiterals.toBooleanOutcomes ==
BooleanLiteralSet.get(!condition)) {
// Use the restricted left type, since the right side never gets
// evaluated.
type = leftType;
literals = leftLiterals;
} else {
// Use the join of the restricted left type knowing the outcome of the
// ToBoolean predicate and of the right type.
type = leftType.getLeastSupertype(rightType);
literals =
getBooleanOutcomePair(leftLiterals, rightLiterals, condition);
}
// Exclude the boolean type if the literal set is empty because a boolean
// can never actually be returned.
if (literals.booleanValues == BooleanLiteralSet.EMPTY &&
getNativeType(BOOLEAN_TYPE).isSubtype(type)) {
// Exclusion only make sense for a union type.
if (type.isUnionType()) {
type = type.toMaybeUnionType().getRestrictedUnion(
getNativeType(BOOLEAN_TYPE));
}
}
} else {
type = null;
literals = new BooleanOutcomePair(
BooleanLiteralSet.BOTH, BooleanLiteralSet.BOTH,
leftLiterals.getJoinedFlowScope(),
rightLiterals.getJoinedFlowScope());
}
n.setJSType(type);
return literals;
}
private BooleanOutcomePair traverseWithinShortCircuitingBinOp(Node n,
FlowScope scope) {
switch (n.getType()) {
case Token.AND:
return traverseAnd(n, scope);
case Token.OR:
return traverseOr(n, scope);
default:
scope = traverse(n, scope);
return newBooleanOutcomePair(n.getJSType(), scope);
}
}
/**
* Infers the boolean outcome pair that can be taken by a
* short-circuiting binary operation ({@code &&} or {@code ||}).
* @see #getBooleanOutcomes(BooleanLiteralSet, BooleanLiteralSet, boolean)
*/
BooleanOutcomePair getBooleanOutcomePair(BooleanOutcomePair left,
BooleanOutcomePair right, boolean condition) {
return new BooleanOutcomePair(
getBooleanOutcomes(left.toBooleanOutcomes, right.toBooleanOutcomes,
condition),
getBooleanOutcomes(left.booleanValues, right.booleanValues, condition),
left.getJoinedFlowScope(), right.getJoinedFlowScope());
}
/**
* Infers the boolean literal set that can be taken by a
* short-circuiting binary operation ({@code &&} or {@code ||}).
* @param left the set of possible {@code ToBoolean} predicate results for
* the expression on the left side of the operator
* @param right the set of possible {@code ToBoolean} predicate results for
* the expression on the right side of the operator
* @param condition the left side {@code ToBoolean} predicate result that
* causes the right side to get evaluated (i.e. not short-circuited)
* @return a set of possible {@code ToBoolean} predicate results for the
* entire expression
*/
static BooleanLiteralSet getBooleanOutcomes(BooleanLiteralSet left,
BooleanLiteralSet right, boolean condition) {
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>
return right.union(left.intersection(BooleanLiteralSet.get(!condition)));
}
/**
* When traversing short-circuiting binary operations, we need to keep track
* of two sets of boolean literals:
* 1. {@code toBooleanOutcomes}: boolean literals as converted from any types,
* 2. {@code booleanValues}: boolean literals from just boolean types.
*/
private final class BooleanOutcomePair {
final BooleanLiteralSet toBooleanOutcomes;
final BooleanLiteralSet booleanValues;
// The scope if only half of the expression executed, when applicable.
final FlowScope leftScope;
// The scope when the whole expression executed.
final FlowScope rightScope;
// The scope when we don't know how much of the expression is executed.
FlowScope joinedScope = null;
BooleanOutcomePair(
BooleanLiteralSet toBooleanOutcomes, BooleanLiteralSet booleanValues,
FlowScope leftScope, FlowScope rightScope) {
this.toBooleanOutcomes = toBooleanOutcomes;
this.booleanValues = booleanValues;
this.leftScope = leftScope;
this.rightScope = rightScope;
}
/**
* Gets the safe estimated scope without knowing if all of the
* subexpressions will be evaluated.
*/
FlowScope getJoinedFlowScope() {
if (joinedScope == null) {
if (leftScope == rightScope) {
joinedScope = rightScope;
} else {
joinedScope = join(leftScope, rightScope);
}
}
return joinedScope;
}
/**
* Gets the outcome scope if we do know the outcome of the entire
* expression.
*/
FlowScope getOutcomeFlowScope(int nodeType, boolean outcome) {
if (nodeType == Token.AND && outcome ||
nodeType == Token.OR && !outcome) {
// We know that the whole expression must have executed.
return rightScope;
} else {
return getJoinedFlowScope();
}
}
}
private BooleanOutcomePair newBooleanOutcomePair(
JSType jsType, FlowScope flowScope) {
if (jsType == null) {
return new BooleanOutcomePair(
BooleanLiteralSet.BOTH, BooleanLiteralSet.BOTH, flowScope, flowScope);
}
return new BooleanOutcomePair(jsType.getPossibleToBooleanOutcomes(),
registry.getNativeType(BOOLEAN_TYPE).isSubtype(jsType) ?
BooleanLiteralSet.BOTH : BooleanLiteralSet.EMPTY,
flowScope, flowScope);
}
private void redeclareSimpleVar(
FlowScope scope, Node nameNode, JSType varType) {
Preconditions.checkState(nameNode.isName());
String varName = nameNode.getString();
if (varType == null) {
varType = getNativeType(JSTypeNative.UNKNOWN_TYPE);
}
if (isUnflowable(syntacticScope.getVar(varName))) {
return;
}
scope.inferSlotType(varName, varType);
}
private boolean isUnflowable(Var v) {
return v != null && v.isLocal() && v.isMarkedEscaped() &&
// It's OK to flow a variable in the scope where it's escaped.
v.getScope() == syntacticScope;
}
/**
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS><Integer> nodeTypes;
private final boolean include;
/**
* Creates an abstract pruned callback.
* @param nodeTypes the nodes to include in the traversal
*/
public AbstractNodeTypePruningCallback(Set<Integer> nodeTypes) {
this(nodeTypes, true);
}
/**
* Creates an abstract pruned callback.
* @param nodeTypes the nodes to include/exclude in the traversal
* @param include whether to include or exclude the nodes in the traversal
*/
public AbstractNodeTypePruningCallback(Set<Integer> nodeTypes,
boolean include) {
this.nodeTypes = nodeTypes;
this.include = include;
}
@Override
public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
return include == nodeTypes.contains(n.getType());
}
}
/**
* Creates a node traversal using the specified callback interface.
*/
public NodeTraversal(AbstractCompiler compiler, Callback cb) {
this(compiler, cb, new SyntacticScopeCreator(compiler));
}
/**
* Creates a node traversal using the specified callback interface
* and the scope creator.
*/
public NodeTraversal(AbstractCompiler compiler, Callback cb,
ScopeCreator scopeCreator) {
this.callback = cb;
if (cb instanceof ScopedCallback) {
this.scopeCallback = (ScopedCallback) cb;
}
this.compiler = compiler;
this.inputId = null;
this.sourceName = "";
this.scopeCreator = scopeCreator;
}
private void throwUnexpectedException(Exception unexpectedException) {
// If there's an unexpected exception, try to get the
// line number of the code that caused it.
String message = unexpectedException.getMessage();
// TODO(user): It is possible to get more information if curNode or
// its parent is missing. We still have the scope stack in which it is still
// very useful to find out at least which function caused the exception.
if (inputId != null) {
message =
unexpectedException.getMessage() + "\n" +
formatNodeContext("Node", curNode) +
(curNode == null ?
"" :
formatNodeContext("Parent", curNode.getParent()));
}
compiler.throwInternalError(message, unexpectedException);
}
private String formatNodeContext(String label, Node n) {
if (n == null) {
return " " + label + ": NULL";
}
return " " + label + "(" + n.toString(false, false, false) + "): "
+ formatNodePosition(n);
}
/**
* Traverses a parse tree recursively.
*/
public void traverse(Node root) {
try {
inputId = NodeUtil.getInputId(root);
sourceName = "";
curNode = root;
pushScope(root);
// null parent ensures that the shallow callbacks will traverse root
traverseBranch(root, null);
popScope();
} catch (Exception unexpectedException) {
throwUnexpectedException(unexpectedException);
}
}
public void traverseRoots(Node ... roots) {
traverseRoots(Lists.newArrayList(roots));
}
public void traverseRoots(List<Node> roots) {
if (roots.isEmpty()) {
return;
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>(compiler, cb);
t.traverseRoots(roots);
}
public static void traverseRoots(
AbstractCompiler compiler, Callback cb, Node ... roots) {
NodeTraversal t = new NodeTraversal(compiler, cb);
t.traverseRoots(roots);
}
/**
* Traverses a branch.
*/
private void traverseBranch(Node n, Node parent) {
int type = n.getType();
if (type == Token.SCRIPT) {
inputId = n.getInputId();
sourceName = getSourceName(n);
}
curNode = n;
if (!callback.shouldTraverse(this, n, parent)) {
return;
}
if (type == Token.FUNCTION) {
traverseFunction(n, parent);
} else {
for (Node child = n.getFirstChild(); child != null; ) {
// child could be replaced, in which case our child node
// would no longer point to the true next
Node next = child.getNext();
traverseBranch(child, n);
child = next;
}
}
curNode = n;
callback.visit(this, n, parent);
}
/** Traverses a function. */
private void traverseFunction(Node n, Node parent) {
Preconditions.checkState(n.getChildCount() == 3);
Preconditions.checkState(n.isFunction());
final Node fnName = n.getFirstChild();
boolean isFunctionExpression = (parent != null)
&& NodeUtil.isFunctionExpression(n);
if (!isFunctionExpression) {
// Functions declarations are in the scope containing the declaration.
traverseBranch(fnName, n);
}
curNode = n;
pushScope(n);
if (isFunctionExpression) {
// Function expression names are only accessible within the function
// scope.
traverseBranch(fnName, n);
}
final Node args = fnName.getNext();
final Node body = args.getNext();
// Args
traverseBranch(args, n);
// Body
Preconditions.checkState(body.getNext() == null && body.isBlock(), body);
traverseBranch(body, n);
popScope();
}
/** Examines the functions stack for the last instance of a function node. */
@SuppressWarnings("unchecked")
public Node getEnclosingFunction() {
if (scopes.size() + scopeRoots.size() < 2) {
return null;
} else {
if (scopeRoots.isEmpty()) {
return scopes.peek().getRootNode();
} else {
return scopeRoots.peek();
}
}
}
/** Creates a new scope (e.g. when entering a function). */
private void pushScope(Node node) {
Preconditions.checkState(curNode != null);
compiler.setScope(node);
scopeRoots.push(node);
cfgs.push(null);
if (scopeCallback != null) {
scopeCallback.enterScope(this);
}
}
/** Creates a new scope (e.g. when entering a function). */
private void pushScope(Scope s) {
Preconditions.checkState(curNode != null);
compiler.setScope(s.getRootNode());
scopes.push(s);
cfgs.push(null);
if (scopeCallback !=
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> null) {
scopeCallback.enterScope(this);
}
}
/** Pops back to the previous scope (e.g. when leaving a function). */
private void popScope() {
if (scopeCallback != null) {
scopeCallback.exitScope(this);
}
if (scopeRoots.isEmpty()) {
scopes.pop();
} else {
scopeRoots.pop();
}
cfgs.pop();
if (hasScope()) {
compiler.setScope(getScopeRoot());
}
}
/** Gets the current scope. */
public Scope getScope() {
Scope scope = scopes.isEmpty() ? null : scopes.peek();
if (scopeRoots.isEmpty()) {
return scope;
}
Iterator<Node> it = scopeRoots.descendingIterator();
while (it.hasNext()) {
scope = scopeCreator.createScope(it.next(), scope);
scopes.push(scope);
}
scopeRoots.clear();
// No need to call compiler.setScope; the top scopeRoot is now the top scope
return scope;
}
/** Gets the control flow graph for the current JS scope. */
public ControlFlowGraph<Node> getControlFlowGraph() {
if (cfgs.peek() == null) {
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
cfa.process(null, getScopeRoot());
cfgs.pop();
cfgs.push(cfa.getCfg());
}
return cfgs.peek();
}
/** Returns the current scope's root. */
public Node getScopeRoot() {
if (scopeRoots.isEmpty()) {
return scopes.peek().getRootNode();
} else {
return scopeRoots.peek();
}
}
/**
* Determines whether the traversal is currently in the global scope.
*/
boolean inGlobalScope() {
return getScopeDepth() <= 1;
}
int getScopeDepth() {
return scopes.size() + scopeRoots.size();
}
public boolean hasScope() {
return !(scopes.isEmpty() && scopeRoots.isEmpty());
}
/** Reports a diagnostic (error or warning) */
public void report(Node n, DiagnosticType diagnosticType,
String... arguments) {
JSError error = JSError.make(
getBestSourceFileName(n), n, diagnosticType, arguments);
compiler.report(error);
}
private static String getSourceName(Node n) {
String name = n.getSourceFileName();
return name == null ? "" : name;
}
InputId getInputId() {
return inputId;
}
/**
* Creates a JSError during NodeTraversal.
*
* @param n Determines the line and char position within the source file name
* @param type The DiagnosticType
* @param arguments Arguments to be incorporated into the message
*/
public JSError makeError(Node n, CheckLevel level, DiagnosticType type,
String... arguments) {
return JSError.make(getBestSourceFileName(n), n, level, type, arguments);
}
/**
* Creates a JSError during NodeTraversal.
*
* @param n Determines the line and char position within the source file name
* @param type The DiagnosticType
* @param arguments
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> new Node(Token.FALSE);
}
public static Node nullNode() {
return new Node(Token.NULL);
}
// helper methods
private static Node binaryOp(int token, Node expr1, Node expr2) {
Preconditions.checkState(mayBeExpression(expr1));
Preconditions.checkState(mayBeExpression(expr2));
return new Node(token, expr1, expr2);
}
private static Node unaryOp(int token, Node expr) {
Preconditions.checkState(mayBeExpression(expr));
return new Node(token, expr);
}
private static boolean mayBeExpressionOrEmpty(Node n) {
return n.isEmpty() || mayBeExpression(n);
}
private static boolean isAssignmentTarget(Node n) {
return n.isName() || n.isGetProp() || n.isGetElem();
}
// NOTE: some nodes are neither statements nor expression nodes:
// SCRIPT, LABEL_NAME, PARAM_LIST, CASE, DEFAULT_CASE, CATCH
// GETTER_DEF, SETTER_DEF
/**
* It isn't possible to always determine if a detached node is a expression,
* so make a best guess.
*/
private static boolean mayBeStatementNoReturn(Node n) {
switch (n.getType()) {
case Token.EMPTY:
case Token.FUNCTION:
// EMPTY and FUNCTION are used both in expression and statement
// contexts
return true;
case Token.BLOCK:
case Token.BREAK:
case Token.CONST:
case Token.CONTINUE:
case Token.DEBUGGER:
case Token.DO:
case Token.EXPR_RESULT:
case Token.FOR:
case Token.IF:
case Token.LABEL:
case Token.SWITCH:
case Token.THROW:
case Token.TRY:
case Token.VAR:
case Token.WHILE:
case Token.WITH:
return true;
default:
return false;
}
}
/**
* It isn't possible to always determine if a detached node is a expression,
* so make a best guess.
*/
private static boolean mayBeStatement(Node n) {
if (!mayBeStatementNoReturn(n)) {
return n.isReturn();
}
return true;
}
/**
* It isn't possible to always determine if a detached node is a expression,
* so make a best guess.
*/
private static boolean mayBeExpression(Node n) {
switch (n.getType()) {
case Token.FUNCTION:
// FUNCTION is used both in expression and statement
// contexts.
return true;
case Token.ADD:
case Token.AND:
case Token.ARRAYLIT:
case Token.ASSIGN:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.ASSIGN_URSH:
case Token.ASSIGN_ADD:
case Token.ASSIGN_SUB:
case Token.ASSIGN_MUL:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
case Token.BITAND:
case Token.BITOR:
case Token.BITNOT:
case Token.
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> */
public boolean isOptionalArg() {
return root.getType() == Token.EQUALS;
}
/**
* @return Whether this expression denotes a rest args {@code @param}.
*/
public boolean isVarArgs() {
return root.getType() == Token.ELLIPSIS;
}
/**
* Evaluates the type expression into a {@code JSType} object.
*/
public JSType evaluate(StaticScope<JSType> scope, JSTypeRegistry registry) {
JSType type = registry.createFromTypeNodes(root, sourceName, scope);
root.setJSType(type);
return type;
}
@Override
public boolean equals(Object other) {
return other instanceof JSTypeExpression &&
((JSTypeExpression) other).root.isEquivalentTo(root);
}
@Override
public int hashCode() {
return root.toStringTree().hashCode();
}
/**
* @return The source for this type expression. Note that it will not
* contain an expression if there's an @override tag.
*/
public Node getRoot() {
return root;
}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>Map() {
this(Maps.<String, Property>newTreeMap());
}
private PropertyMap(Map<String, Property> underlyingMap) {
this.properties = underlyingMap;
}
static PropertyMap immutableEmptyMap() {
return EMPTY_MAP;
}
void setParentSource(ObjectType ownerType) {
if (this != EMPTY_MAP) {
this.parentSource = ownerType;
}
}
/** Returns the direct parent of this property map. */
PropertyMap getPrimaryParent() {
if (parentSource == null) {
return null;
}
ObjectType iProto = parentSource.getImplicitPrototype();
return iProto == null ? null : iProto.getPropertyMap();
}
/**
* Returns the secondary parents of this property map, for interfaces that
* need multiple inheritance.
*/
Iterable<PropertyMap> getSecondaryParents() {
if (parentSource == null) {
return ImmutableList.of();
}
Iterable<ObjectType> extendedInterfaces =
parentSource.getCtorExtendedInterfaces();
// Most of the time, this will be empty.
if (Iterables.isEmpty(extendedInterfaces)) {
return ImmutableList.of();
}
return Iterables.transform(extendedInterfaces, PROP_MAP_FROM_TYPE);
}
Property getSlot(String name) {
if (properties.containsKey(name)) {
return properties.get(name);
}
PropertyMap primaryParent = getPrimaryParent();
if (primaryParent != null) {
Property prop = primaryParent.getSlot(name);
if (prop != null) {
return prop;
}
}
for (PropertyMap p : getSecondaryParents()) {
if (p != null) {
Property prop = p.getSlot(name);
if (prop != null) {
return prop;
}
}
}
return null;
}
Property getOwnProperty(String propertyName) {
return properties.get(propertyName);
}
int getPropertiesCount() {
PropertyMap primaryParent = getPrimaryParent();
if (primaryParent == null) {
return this.properties.size();
}
Set<String> props = Sets.newHashSet();
collectPropertyNames(props);
return props.size();
}
boolean hasOwnProperty(String propertyName) {
return properties.get(propertyName) != null;
}
boolean hasProperty(String propertyName) {
return getSlot(propertyName) != null;
}
Set<String> getOwnPropertyNames() {
return properties.keySet();
}
void collectPropertyNames(Set<String> props) {
for (String prop : properties.keySet()) {
props.add(prop);
}
PropertyMap primaryParent = getPrimaryParent();
if (primaryParent != null) {
primaryParent.collectPropertyNames(props);
}
for (PropertyMap p : getSecondaryParents()) {
if (p != null) {
p.collectPropertyNames(props);
}
}
}
boolean removeProperty(String name) {
return properties.remove(name) != null;
}
void putProperty(String name, Property newProp) {
Property oldProp = properties.get(name);
if (oldProp != null) {
// This is to keep previously inferred JsDoc info, e.g., in
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> far. But the compile-time
// costs would be unacceptable.
//
// We do one pass where we do typed scope creation for all scopes
// in pre-order.
//
// Then we do a second pass where we do all type inference
// (type propagation) in pre-order.
//
// We use a memoized scope creator so that we never create a scope
// more than once.
//
// This will allow us to handle cases like:
// var ns = {};
// (function() { /** JSDoc */ ns.method = function() {}; })();
// ns.method();
// In this code, we need to build the symbol table for the inner scope in
// order to propagate the type of ns.method in the outer scope.
(new NodeTraversal(
compiler, new FirstScopeBuildingCallback(), scopeCreator))
.traverseWithScope(node, topScope);
for (Scope s : scopeCreator.getAllMemoizedScopes()) {
s.resolveTypes();
}
(new NodeTraversal(
compiler, new SecondScopeBuildingCallback(), scopeCreator))
.traverseWithScope(node, topScope);
}
void inferScope(Node n, Scope scope) {
TypeInference typeInference =
new TypeInference(
compiler, computeCfg(n), reverseInterpreter, scope,
assertionFunctionsMap);
try {
typeInference.analyze();
// Resolve any new type names found during the inference.
compiler.getTypeRegistry().resolveTypesInScope(scope);
} catch (DataFlowAnalysis.MaxIterationsExceededException e) {
compiler.report(JSError.make(n.getSourceFileName(), n, DATAFLOW_ERROR));
}
}
private class FirstScopeBuildingCallback extends AbstractScopedCallback {
@Override
public void enterScope(NodeTraversal t) {
t.getScope();
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
// Do nothing
}
}
private class SecondScopeBuildingCallback extends AbstractScopedCallback {
@Override
public void enterScope(NodeTraversal t) {
// Only infer the entry root, rather than the scope root.
// This ensures that incremental compilation only touches the root
// that's been swapped out.
inferScope(t.getCurrentNode(), t.getScope());
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
// Do nothing
}
}
private ControlFlowGraph<Node> computeCfg(Node n) {
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, false);
cfa.process(null, n);
return cfa.getCfg();
}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>/*
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Rhino code, released
* May 6, 1999.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1997-1999
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Bob Jervis
* Google Inc.
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU General Public License Version 2 or later (the "GPL"), in which
* case the provisions of the GPL are applicable instead of those above. If
* you wish to allow use of your version of this file only under the terms of
* the GPL and not to allow others to use your version of this file under the
* MPL, indicate your decision by deleting the provisions above and replacing
* them with the notice and other provisions required by the GPL. If you do
* not delete the provisions above, a recipient may use your version of this
* file under either the MPL or the GPL.
*
* ***** END LICENSE BLOCK ***** */
package com.google.javascript.rhino.jstype;
import com.google.javascript.rhino.Node;
/**
* The {@code StaticScope} interface must be implemented by any object that
* defines variables for the purposes of static analysis. It is distinguished
* from the {@code Scriptable} class that Rhino normally uses to represent a
* run-time scope.
*
* @param <T> The type of information stored about the slot
*/
public interface StaticScope<T> {
/**
* Returns the root node associated with this scope. May be null.
*/
Node getRootNode();
/** Returns the scope enclosing this one or null if none. */
StaticScope<T> getParentScope();
/**
* Returns any defined slot within this scope for this name. This call
* continues searching through parent scopes if a slot with this name is not
* found in the current scope.
* @param name The name of the variable slot to look up.
* @return The defined slot for the variable, or {@code null} if no
* definition exists.
*/
StaticSlot<T> getSlot(String name);
/** Like {@code getSlot} but does not recurse into parent scopes. */
StaticSlot<T> getOwnSlot(String name);
/** Returns the expected type of {@code this} in the current scope. */
T getTypeOfThis();
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>CompilerPass pass);
/**
* Returns the root node of the AST, which includes both externs and source.
*/
abstract Node getRoot();
// TODO(bashir) It would be good to extract a single dumb data object with
// only getters and setters that keeps all global information we keep for a
// compiler instance. Then move some of the functions of this class there.
/**
* Updates the list of references for variables in global scope.
*
* @param refMapPatch Maps each variable to all of its references; may contain
* references collected from the whole AST or only a SCRIPT sub-tree.
* @param collectionRoot The root of sub-tree in which reference collection
* has been done. This should either be a SCRIPT node (if collection is
* done on a single file) or it is assumed that collection is on full AST.
*/
abstract void updateGlobalVarReferences(Map<Var, ReferenceCollection>
refMapPatch, Node collectionRoot);
/**
* This can be used to get the list of all references to all global variables
* based on all previous calls to {@code updateGlobalVarReferences}.
*
* @return The reference collection map associated to global scope variable.
*/
abstract GlobalVarReferenceMap getGlobalVarReferences();
/**
* @return a CompilerInput that can be modified to add addition extern
* definitions;
*/
abstract CompilerInput getSynthesizedExternsInput();
/**
* @return a number in [0,1] range indicating an approximate progress of the
* last compile. Note this should only be used as a hint and no assumptions
* should be made on accuracy, even a completed compile may choose not to set
* this to 1.0 at the end.
*/
public abstract double getProgress();
/**
* Gets the last pass name set by setProgress.
*/
abstract String getLastPassName();
/**
* Sets the progress percentage as well as the name of the last pass that
* ran (if available).
* @param progress A precentage expressed as a double in the range [0, 1].
* Use -1 if you just want to set the last pass name.
*/
abstract void setProgress(double progress, @Nullable String lastPassName);
/**
* The subdir js/ contains libraries of code that we inject
* at compile-time only if requested by this function.
*
* Notice that these libraries will almost always create global symbols.
*
* @param resourceName The name of the library. For example, if "base" is
* is specified, then we load js/base.js
* @return If new code was injected, returns the last expression node of the
* library. If the caller needs to add additional code, they should add
* it as the next sibling of this node. If new code was not injected,
* returns null.
*/
abstract Node ensureLibraryInjected(String resourceName);
/**
* Stores the "new" Rhino parse tree for a given source file.
* @param sourceName The source file name.
* @param astRoot The "new" Rhino parse tree.
*/
abstract void setOldParseTree(String sourceName, AstRoot astRoot);
/**
* Gets an old format parse
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>/*
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Rhino code, released
* May 6, 1999.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1997-1999
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Bob Jervis
* Google Inc.
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU General Public License Version 2 or later (the "GPL"), in which
* case the provisions of the GPL are applicable instead of those above. If
* you wish to allow use of your version of this file only under the terms of
* the GPL and not to allow others to use your version of this file under the
* MPL, indicate your decision by deleting the provisions above and replacing
* them with the notice and other provisions required by the GPL. If you do
* not delete the provisions above, a recipient may use your version of this
* file under either the MPL or the GPL.
*
* ***** END LICENSE BLOCK ***** */
package com.google.javascript.rhino.jstype;
import com.google.common.collect.ImmutableList;
import com.google.javascript.rhino.Node;
/**
* A builder class for function and arrow types.
*
* If you need to build an interface constructor,
* use {@link JSTypeRegistry#createInterfaceType}.
*
* @author nicksantos@google.com (Nick Santos)
*/
public final class FunctionBuilder {
private final JSTypeRegistry registry;
private String name = null;
private Node sourceNode = null;
private Node parametersNode = null;
private JSType returnType = null;
private JSType typeOfThis = null;
private TemplateTypeMap templateTypeMap = null;
private boolean inferredReturnType = false;
private boolean isConstructor = false;
private boolean isNativeType = false;
public FunctionBuilder(JSTypeRegistry registry) {
this.registry = registry;
}
/** Set the name of the function type. */
public FunctionBuilder withName(String name) {
this.name = name;
return this;
}
/** Set the source node of the function type. */
public FunctionBuilder withSourceNode(Node sourceNode) {
this.sourceNode = sourceNode;
return this;
}
/** Set the parameters of the function type from a FunctionParamBuilder. */
public FunctionBuilder withParams(FunctionParamBuilder params) {
this.parametersNode = params
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>.build();
return this;
}
/**
* Set the parameters of the function type with a specially-formatted node.
*/
public FunctionBuilder withParamsNode(Node parametersNode) {
this.parametersNode = parametersNode;
return this;
}
/** Set the return type. */
public FunctionBuilder withReturnType(JSType returnType) {
this.returnType = returnType;
return this;
}
/** Set the return type and whether it's inferred. */
public FunctionBuilder withReturnType(JSType returnType, boolean inferred) {
this.returnType = returnType;
this.inferredReturnType = inferred;
return this;
}
/** Sets an inferred return type. */
public FunctionBuilder withInferredReturnType(JSType returnType) {
this.returnType = returnType;
this.inferredReturnType = true;
return this;
}
/** Set the "this" type. */
public FunctionBuilder withTypeOfThis(JSType typeOfThis) {
this.typeOfThis = typeOfThis;
return this;
}
/** Set the template name. */
public FunctionBuilder withTemplateKeys(
ImmutableList<TemplateType> templateKeys) {
this.templateTypeMap = registry.createTemplateTypeMap(templateKeys, null);
return this;
}
/** Make this a constructor. */
public FunctionBuilder forConstructor() {
this.isConstructor = true;
return this;
}
/** Set whether this is a constructor. */
public FunctionBuilder setIsConstructor(boolean isConstructor) {
this.isConstructor = isConstructor;
return this;
}
/** Make this a native type. */
FunctionBuilder forNativeType() {
this.isNativeType = true;
return this;
}
/** Copies all the information from another function type. */
public FunctionBuilder copyFromOtherFunction(FunctionType otherType) {
this.name = otherType.getReferenceName();
this.sourceNode = otherType.getSource();
this.parametersNode = otherType.getParametersNode();
this.returnType = otherType.getReturnType();
this.typeOfThis = otherType.getTypeOfThis();
this.templateTypeMap = otherType.getTemplateTypeMap();
this.isConstructor = otherType.isConstructor();
this.isNativeType = otherType.isNativeObjectType();
return this;
}
/** Construct a new function type. */
public FunctionType build() {
return new FunctionType(registry, name, sourceNode,
new ArrowType(registry, parametersNode, returnType, inferredReturnType),
typeOfThis, templateTypeMap, isConstructor, isNativeType);
}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> */
private Scope getFunctionScope() {
return cache.functionScope;
}
/** Whether this flows from a bottom scope. */
private boolean flowsFromBottom() {
return getFunctionScope().isBottom();
}
/**
* Creates an entry lattice for the flow.
*/
public static LinkedFlowScope createEntryLattice(Scope scope) {
return new LinkedFlowScope(new FlatFlowScopeCache(scope));
}
@Override
public void inferSlotType(String symbol, JSType type) {
Preconditions.checkState(!frozen);
lastSlot = new LinkedFlowSlot(symbol, type, lastSlot);
depth++;
cache.dirtySymbols.add(symbol);
}
@Override
public void inferQualifiedSlot(Node node, String symbol, JSType bottomType,
JSType inferredType) {
Scope functionScope = getFunctionScope();
if (functionScope.isLocal()) {
if (functionScope.getVar(symbol) == null && !functionScope.isBottom()) {
functionScope.declare(symbol, node, bottomType, null);
}
inferSlotType(symbol, inferredType);
}
}
@Override
public JSType getTypeOfThis() {
return cache.functionScope.getTypeOfThis();
}
@Override
public Node getRootNode() {
return getFunctionScope().getRootNode();
}
@Override
public StaticScope<JSType> getParentScope() {
return getFunctionScope().getParentScope();
}
/**
* Get the slot for the given symbol.
*/
@Override
public StaticSlot<JSType> getSlot(String name) {
if (cache.dirtySymbols.contains(name)) {
for (LinkedFlowSlot slot = lastSlot;
slot != null; slot = slot.parent) {
if (slot.getName().equals(name)) {
return slot;
}
}
}
return cache.getSlot(name);
}
@Override
public StaticSlot<JSType> getOwnSlot(String name) {
throw new UnsupportedOperationException();
}
@Override
public FlowScope createChildFlowScope() {
frozen = true;
if (depth > MAX_DEPTH) {
if (flattened == null) {
flattened = new FlatFlowScopeCache(this);
}
return new LinkedFlowScope(flattened);
}
return new LinkedFlowScope(this);
}
/**
* Iterate through all the linked flow scopes before this one.
* If there's one and only one slot defined between this scope
* and the blind scope, return it.
*/
@Override
public StaticSlot<JSType> findUniqueRefinedSlot(FlowScope blindScope) {
StaticSlot<JSType> result = null;
for (LinkedFlowScope currentScope = this;
currentScope != blindScope;
currentScope = currentScope.parent) {
for (LinkedFlowSlot currentSlot = currentScope.lastSlot;
currentSlot != null &&
(currentScope.parent == null ||
currentScope.parent.lastSlot != currentSlot);
currentSlot = currentSlot.parent) {
if (result == null) {
result = currentSlot;
} else if (!currentSlot.getName().equals(result.getName())) {
return null;
}
}
}
return result
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>;
}
/**
* Look through the given scope, and try to find slots where it doesn't
* have enough type information. Then fill in that type information
* with stuff that we've inferred in the local flow.
*/
@Override
public void completeScope(StaticScope<JSType> staticScope) {
Scope scope = (Scope) staticScope;
for (Iterator<Var> it = scope.getVars(); it.hasNext();) {
Var var = it.next();
if (var.isTypeInferred()) {
JSType type = var.getType();
if (type == null || type.isUnknownType()) {
JSType flowType = getSlot(var.getName()).getType();
var.setType(flowType);
}
}
}
}
/**
* Remove flow scopes that add nothing to the flow.
*/
// NOTE(nicksantos): This function breaks findUniqueRefinedSlot, because
// findUniqueRefinedSlot assumes that this scope is a direct descendant
// of blindScope. This is not necessarily true if this scope has been
// optimize()d and blindScope has not. This should be fixed. For now,
// we only use optimize() where we know that we won't have to do
// a findUniqueRefinedSlot on it.
@Override
public LinkedFlowScope optimize() {
LinkedFlowScope current;
for (current = this;
current.parent != null &&
current.lastSlot == current.parent.lastSlot;
current = current.parent) {}
return current;
}
/** Join the two FlowScopes. */
static class FlowScopeJoinOp extends JoinOp.BinaryJoinOp<FlowScope> {
@SuppressWarnings("unchecked")
@Override
public FlowScope apply(FlowScope a, FlowScope b) {
// To join the two scopes, we have to
LinkedFlowScope linkedA = (LinkedFlowScope) a;
LinkedFlowScope linkedB = (LinkedFlowScope) b;
linkedA.frozen = true;
linkedB.frozen = true;
if (linkedA.optimize() == linkedB.optimize()) {
return linkedA.createChildFlowScope();
}
return new LinkedFlowScope(new FlatFlowScopeCache(linkedA, linkedB));
}
}
@Override
public boolean equals(Object other) {
if (other instanceof LinkedFlowScope) {
LinkedFlowScope that = (LinkedFlowScope) other;
if (this.optimize() == that.optimize()) {
return true;
}
// If two flow scopes are in the same function, then they could have
// two possible function scopes: the real one and the BOTTOM scope.
// If they have different function scopes, we *should* iterate through all
// the variables in each scope and compare. However, 99.9% of the time,
// they're not equal. And the other .1% of the time, we can pretend
// they're equal--this just means that data flow analysis will have
// to propagate the entry lattice a little bit further than it
// really needs to. Everything will still come out ok.
if (this.getFunctionScope() != that.getFunctionScope()) {
return false;
}
if (cache == that.cache) {
// If the
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> two flow scopes have the same cache, then we can check
// equality a lot faster: by just looking at the "dirty" elements
// in the cache, and comparing them in both scopes.
for (String name : cache.dirtySymbols) {
if (diffSlots(getSlot(name), that.getSlot(name))) {
return false;
}
}
return true;
}
Map<String, StaticSlot<JSType>> myFlowSlots = allFlowSlots();
Map<String, StaticSlot<JSType>> otherFlowSlots = that.allFlowSlots();
for (StaticSlot<JSType> slot : myFlowSlots.values()) {
if (diffSlots(slot, otherFlowSlots.get(slot.getName()))) {
return false;
}
otherFlowSlots.remove(slot.getName());
}
for (StaticSlot<JSType> slot : otherFlowSlots.values()) {
if (diffSlots(slot, myFlowSlots.get(slot.getName()))) {
return false;
}
}
return true;
}
return false;
}
/**
* Determines whether two slots are meaningfully different for the
* purposes of data flow analysis.
*/
private boolean diffSlots(StaticSlot<JSType> slotA,
StaticSlot<JSType> slotB) {
boolean aIsNull = slotA == null || slotA.getType() == null;
boolean bIsNull = slotB == null || slotB.getType() == null;
if (aIsNull && bIsNull) {
return false;
} else if (aIsNull ^ bIsNull) {
return true;
}
// Both slots and types must be non-null.
return slotA.getType().differsFrom(slotB.getType());
}
/**
* Gets all the symbols that have been defined before this point
* in the current flow. Does not return slots that have not changed during
* the flow.
*
* For example, consider the code:
* <code>
* var x = 3;
* function f() {
* var y = 5;
* y = 6; // FLOW POINT
* var z = y;
* return z;
* }
* </code>
* A FlowScope at FLOW POINT will return a slot for y, but not
* a slot for x or z.
*/
private Map<String, StaticSlot<JSType>> allFlowSlots() {
Map<String, StaticSlot<JSType>> slots = Maps.newHashMap();
for (LinkedFlowSlot slot = lastSlot;
slot != null; slot = slot.parent) {
if (!slots.containsKey(slot.getName())) {
slots.put(slot.getName(), slot);
}
}
for (Map.Entry<String, StaticSlot<JSType>> symbolEntry : cache.symbols.entrySet()) {
if (!slots.containsKey(symbolEntry.getKey())) {
slots.put(symbolEntry.getKey(), symbolEntry.getValue());
}
}
return slots;
}
@Override
public int hashCode() {
throw new UnsupportedOperationException();
}
/**
* A static slot that can be used in a linked list.
*/
private static class LinkedFlowSlot extends SimpleSlot {
final LinkedFlowSlot
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>HashSet(symbols.keySet());
symbolNames.addAll(slotsB.keySet());
for (String name : symbolNames) {
StaticSlot<JSType> slotA = slotsA.get(name);
StaticSlot<JSType> slotB = slotsB.get(name);
JSType joinedType = null;
if (slotB == null || slotB.getType() == null) {
StaticSlot<JSType> fnSlot
= joinedScopeB.getFunctionScope().getSlot(name);
JSType fnSlotType = fnSlot == null ? null : fnSlot.getType();
if (fnSlotType == null) {
// Case #1 -- already inserted.
} else {
// Case #3
joinedType = slotA.getType().getLeastSupertype(fnSlotType);
}
} else if (slotA == null || slotA.getType() == null) {
StaticSlot<JSType> fnSlot
= joinedScopeA.getFunctionScope().getSlot(name);
JSType fnSlotType = fnSlot == null ? null : fnSlot.getType();
if (fnSlotType == null) {
// Case #2
symbols.put(name, slotB);
} else {
// Case #4
joinedType = slotB.getType().getLeastSupertype(fnSlotType);
}
} else {
// Case #5
joinedType =
slotA.getType().getLeastSupertype(slotB.getType());
}
if (joinedType != null) {
symbols.put(name, new SimpleSlot(name, joinedType, true));
}
}
}
/**
* Get the slot for the given symbol.
*/
public StaticSlot<JSType> getSlot(String name) {
if (symbols.containsKey(name)) {
return symbols.get(name);
} else {
return functionScope.getSlot(name);
}
}
}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS><T> visitor) {
return visitor.caseUnknownType();
}
@Override <T> T visit(RelationshipVisitor<T> visitor, JSType that) {
return visitor.caseUnknownType(this, that);
}
@Override
String toStringHelper(boolean forAnnotations) {
return getReferenceName();
}
@Override
boolean defineProperty(String propertyName, JSType type,
boolean inferred, Node propertyNode) {
// nothing to define
return true;
}
@Override
public ObjectType getImplicitPrototype() {
return null;
}
@Override
public FunctionType getConstructor() {
return null;
}
@Override
public String getReferenceName() {
return isChecked ? "??" : "?";
}
@Override
public String getDisplayName() {
return "Unknown";
}
@Override
public boolean hasDisplayName() {
return true;
}
@Override
public BooleanLiteralSet getPossibleToBooleanOutcomes() {
return BooleanLiteralSet.BOTH;
}
@Override
JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) {
return this;
}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> };
}
public static class LocationMapping {
final String prefix;
final String replacement;
public LocationMapping(String prefix, String replacement) {
this.prefix = prefix;
this.replacement = replacement;
}
}
private final SourceMapGenerator generator;
private List<LocationMapping> prefixMappings = Collections.emptyList();
private final Map<String, String> sourceLocationFixupCache =
Maps.newHashMap();
private SourceMap(SourceMapGenerator generator) {
this.generator = generator;
}
public void addMapping(
Node node,
FilePosition outputStartPosition,
FilePosition outputEndPosition) {
String sourceFile = node.getSourceFileName();
// If the node does not have an associated source file or
// its line number is -1, then the node does not have sufficient
// information for a mapping to be useful.
if (sourceFile == null || node.getLineno() < 0) {
return;
}
sourceFile = fixupSourceLocation(sourceFile);
String originalName = (String) node.getProp(Node.ORIGINALNAME_PROP);
// Strangely, Rhino source lines are one based but columns are
// zero based.
// We don't change this for the v1 or v2 source maps but for
// v3 we make them both 0 based.
int lineBaseOffset = 1;
if (generator instanceof SourceMapGeneratorV1
|| generator instanceof SourceMapGeneratorV2) {
lineBaseOffset = 0;
}
generator.addMapping(
sourceFile, originalName,
new FilePosition(node.getLineno() - lineBaseOffset, node.getCharno()),
outputStartPosition, outputEndPosition);
}
/**
* @param sourceFile The source file location to fixup.
* @return a remapped source file.
*/
private String fixupSourceLocation(String sourceFile) {
if (prefixMappings.isEmpty()) {
return sourceFile;
}
String fixed = sourceLocationFixupCache.get(sourceFile);
if (fixed != null) {
return fixed;
}
// Replace the first prefix found with its replacement
for (LocationMapping mapping : prefixMappings) {
if (sourceFile.startsWith(mapping.prefix)) {
fixed = mapping.replacement + sourceFile.substring(
mapping.prefix.length());
break;
}
}
// If none of the mappings match then use the original file path.
if (fixed == null) {
fixed = sourceFile;
}
sourceLocationFixupCache.put(sourceFile, fixed);
return fixed;
}
public void appendTo(Appendable out, String name) throws IOException {
generator.appendTo(out, name);
}
public void reset() {
generator.reset();
sourceLocationFixupCache.clear();
}
public void setStartingPosition(int offsetLine, int offsetIndex) {
generator.setStartingPosition(offsetLine, offsetIndex);
}
public void setWrapperPrefix(String prefix) {
generator.setWrapperPrefix(prefix);
}
public void validate(boolean validate) {
generator.validate(validate);
}
/**
* @param sourceMapLocationMappings
*/
public void setPrefixMappings(List<LocationMapping>
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>
result.addAll(module.getInputs());
}
return result;
}
private Collection<CompilerInput> createEntryPointInputs(
DependencyOptions depOptions,
List<CompilerInput> inputs,
SortedDependencies<CompilerInput> sorter)
throws MissingModuleException, MissingProvideException {
Set<CompilerInput> entryPointInputs = Sets.newLinkedHashSet();
Map<String, JSModule> modulesByName = getModulesByName();
if (depOptions.shouldPruneDependencies()) {
if (!depOptions.shouldDropMoochers()) {
entryPointInputs.addAll(sorter.getInputsWithoutProvides());
}
for (String entryPoint : depOptions.getEntryPoints()) {
// An entry point is either formatted as:
// 'foo.bar' - peg foo.bar to its current module
// 'modC:foo.bar' - peg foo.bar to modC
String inputName = entryPoint;
int splitPoint = entryPoint.indexOf(':');
CompilerInput entryPointInput = null;
if (splitPoint != -1) {
String moduleName = entryPoint.substring(0, splitPoint);
inputName = entryPoint.substring(
Math.min(splitPoint + 1, entryPoint.length() - 1));
JSModule module = modulesByName.get(moduleName);
if (module == null) {
throw new MissingModuleException(moduleName);
} else {
entryPointInput = sorter.getInputProviding(inputName);
entryPointInput.overrideModule(module);
}
} else {
entryPointInput = sorter.getInputProviding(inputName);
}
entryPointInputs.add(entryPointInput);
}
CompilerInput baseJs = sorter.maybeGetInputProviding("goog");
if (baseJs != null) {
entryPointInputs.add(baseJs);
}
} else {
entryPointInputs.addAll(inputs);
}
return entryPointInputs;
}
LinkedDirectedGraph<JSModule, String> toGraphvizGraph() {
LinkedDirectedGraph<JSModule, String> graphViz =
LinkedDirectedGraph.create();
for (JSModule module : getAllModules()) {
graphViz.createNode(module);
for (JSModule dep : module.getDependencies()) {
graphViz.createNode(dep);
graphViz.connect(module, "->", dep);
}
}
return graphViz;
}
/**
* A module depth comparator that considers a deeper module to be "less than"
* a shallower module. Uses module names to consistently break ties.
*/
private class InverseDepthComparator implements Comparator<JSModule> {
@Override
public int compare(JSModule m1, JSModule m2) {
return depthCompare(m2, m1);
}
}
private int depthCompare(JSModule m1, JSModule m2) {
if (m1 == m2) {
return 0;
}
int d1 = m1.getDepth();
int d2 = m2.getDepth();
return d1 < d2 ? -1 : d2 == d1 ? m1.getName().compareTo(m2.getName()) : 1;
}
/**
* Exception class for declaring when the modules being fed
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>processing sanity check.
private final boolean sanityCheck;
// Whether extern checks emit error.
private final boolean strictExternCheck;
VarCheck(AbstractCompiler compiler) {
this(compiler, false);
}
VarCheck(AbstractCompiler compiler, boolean sanityCheck) {
this.compiler = compiler;
this.strictExternCheck = compiler.getErrorLevel(
JSError.make("", 0, 0, UNDEFINED_EXTERN_VAR_ERROR)) == CheckLevel.ERROR;
this.sanityCheck = sanityCheck;
}
@Override
public void process(Node externs, Node root) {
// Don't run externs-checking in sanity check mode. Normalization will
// remove duplicate VAR declarations, which will make
// externs look like they have assigns.
if (!sanityCheck) {
NodeTraversal.traverse(compiler, externs, new NameRefInExternsCheck());
}
NodeTraversal.traverseRoots(
compiler, Lists.newArrayList(externs, root), this);
for (String varName : varsToDeclareInExterns) {
createSynthesizedExternVar(varName);
}
}
@Override
public void hotSwapScript(Node scriptRoot, Node originalRoot) {
Preconditions.checkState(scriptRoot.isScript());
NodeTraversal t = new NodeTraversal(compiler, this);
// Note we use the global scope to prevent wrong "undefined-var errors" on
// variables that are defined in other JS files.
t.traverseWithScope(scriptRoot,
SyntacticScopeCreator.generateUntypedTopScope(compiler));
// TODO(bashir) Check if we need to createSynthesizedExternVar like process.
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (!n.isName()) {
return;
}
String varName = n.getString();
// Only a function can have an empty name.
if (varName.isEmpty()) {
Preconditions.checkState(parent.isFunction());
Preconditions.checkState(NodeUtil.isFunctionExpression(parent));
return;
}
// Check if this is a declaration for a var that has been declared
// elsewhere. If so, mark it as a duplicate.
if ((parent.isVar() ||
NodeUtil.isFunctionDeclaration(parent)) &&
varsToDeclareInExterns.contains(varName)) {
createSynthesizedExternVar(varName);
n.addSuppression("duplicate");
}
// Check that the var has been declared.
Scope scope = t.getScope();
Scope.Var var = scope.getVar(varName);
if (var == null) {
if (NodeUtil.isFunctionExpression(parent)) {
// e.g. [ function foo() {} ], it's okay if "foo" isn't defined in the
// current scope.
} else {
// The extern checks are stricter, don't report a second error.
if (!strictExternCheck || !t.getInput().isExtern()) {
t.report(n, UNDEFINED_VAR_ERROR, varName);
}
if (sanityCheck) {
throw new IllegalStateException("Unexpected variable " + varName);
} else {
createSynthesizedExternVar(varName);
scope.getGlobalScope().declare(
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>varName, n,
null, getSynthesizedExternsInput());
}
}
return;
}
CompilerInput currInput = t.getInput();
CompilerInput varInput = var.input;
if (currInput == varInput || currInput == null || varInput == null) {
// The variable was defined in the same file. This is fine.
return;
}
// Check module dependencies.
JSModule currModule = currInput.getModule();
JSModule varModule = varInput.getModule();
JSModuleGraph moduleGraph = compiler.getModuleGraph();
if (!sanityCheck &&
varModule != currModule && varModule != null && currModule != null) {
if (moduleGraph.dependsOn(currModule, varModule)) {
// The module dependency was properly declared.
} else {
if (scope.isGlobal()) {
if (moduleGraph.dependsOn(varModule, currModule)) {
// The variable reference violates a declared module dependency.
t.report(n, VIOLATED_MODULE_DEP_ERROR,
currModule.getName(), varModule.getName(), varName);
} else {
// The variable reference is between two modules that have no
// dependency relationship. This should probably be considered an
// error, but just issue a warning for now.
t.report(n, MISSING_MODULE_DEP_ERROR,
currModule.getName(), varModule.getName(), varName);
}
} else {
t.report(n, STRICT_MODULE_DEP_ERROR,
currModule.getName(), varModule.getName(), varName);
}
}
}
}
/**
* Create a new variable in a synthetic script. This will prevent
* subsequent compiler passes from crashing.
*/
private void createSynthesizedExternVar(String varName) {
Node nameNode = IR.name(varName);
// Mark the variable as constant if it matches the coding convention
// for constant vars.
// NOTE(nicksantos): honestly, I'm not sure how much this matters.
// AFAIK, all people who use the CONST coding convention also
// compile with undeclaredVars as errors. We have some test
// cases for this configuration though, and it makes them happier.
if (compiler.getCodingConvention().isConstant(varName)) {
nameNode.putBooleanProp(Node.IS_CONSTANT_NAME, true);
}
getSynthesizedExternsRoot().addChildToBack(
IR.var(nameNode));
varsToDeclareInExterns.remove(varName);
compiler.reportCodeChange();
}
/**
* A check for name references in the externs inputs. These used to prevent
* a variable from getting renamed, but no longer have any effect.
*/
private class NameRefInExternsCheck extends AbstractPostOrderCallback {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isName()) {
switch (parent.getType()) {
case Token.VAR:
case Token.FUNCTION:
case Token.PARAM_LIST:
// These are okay.
break;
case Token.GETPROP:
if (n == parent.getFirstChild()) {
Scope scope = t.getScope();
Scope.Var var
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> = scope.getVar(n.getString());
if (var == null) {
t.report(n, UNDEFINED_EXTERN_VAR_ERROR, n.getString());
varsToDeclareInExterns.add(n.getString());
}
}
break;
default:
t.report(n, NAME_REFERENCE_IN_EXTERNS_ERROR, n.getString());
Scope scope = t.getScope();
Scope.Var var = scope.getVar(n.getString());
if (var == null) {
varsToDeclareInExterns.add(n.getString());
}
break;
}
}
}
}
/** Lazily create a "new" externs input for undeclared variables. */
private CompilerInput getSynthesizedExternsInput() {
return compiler.getSynthesizedExternsInput();
}
/** Lazily create a "new" externs root for undeclared variables. */
private Node getSynthesizedExternsRoot() {
if (synthesizedExternsRoot == null) {
CompilerInput synthesizedExterns = getSynthesizedExternsInput();
synthesizedExternsRoot = synthesizedExterns.getAstRoot(compiler);
}
return synthesizedExternsRoot;
}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> = t.getScopeRoot();
if (n.isName()) {
if (noLocalVariables) {
return null;
}
key = n.getQualifiedName();
if (scopeNode.isFunction()) {
JSType parentScopeType = t.getScope().getParentScope().getTypeOfThis();
/*
* If the locally defined variable is defined within a function, use
* the function name to create ID.
*/
if (!parentScopeType.isGlobalThisType()) {
key = parentScopeType.toString() + "~" + key;
}
key = NodeUtil.getFunctionName(scopeNode) + "=" + key;
}
} else {
/*
* Only handle cases such as a.b.c.X and not cases where the
* eventful object is stored in an array or uses a function to
* determine the index.
*
* Note: Inheritance changes the name that should be returned here
*/
if (!n.isQualifiedName()) {
return null;
}
key = n.getQualifiedName();
/*
* If it is not a simple variable and doesn't use this, then we assume
* global variable.
*/
Node base = getBase(n);
if (base != null && base.isThis()) {
if (base.getJSType().isUnknownType()) {
// Handle anonymous function created in constructor:
//
// /**
// * @extends {goog.SubDisposable}
// * @constructor */
// speel.Person = function() {
// this.run = function() {
// this.eh = new goog.events.EventHandler();
// }
//};
key = t.getScope().getParentScope().getTypeOfThis().toString() + "~" + key;
} else {
if (n.getFirstChild() == null) {
key = base.getJSType().toString() + "=" + key;
} else {
ObjectType objectType =
ObjectType.cast(dereference(n.getFirstChild().getJSType()));
if (objectType == null) {
return null;
}
ObjectType hObjT = objectType;
String propertyName = n.getLastChild().getString();
while (objectType != null) {
hObjT = objectType;
objectType = objectType.getImplicitPrototype();
if (objectType == null) {
break;
}
if (objectType.getDisplayName().endsWith("prototype")) {
continue;
}
if (!objectType.getPropertyNames().contains(propertyName)) {
break;
}
}
key = hObjT.toString() + "=" + key;
}
}
}
}
return key;
}
@Override
public void process(Node externs, Node root) {
// This pass should not have gotten added in this case
Preconditions.checkArgument(checkingPolicy != DisposalCheckingPolicy.OFF);
// Initialize types
googDisposableType = compiler.getTypeRegistry().getType(DISPOSABLE_TYPE_NAME);
googEventsEventHandlerType = compiler.getTypeRegistry().getType(EVENT_HANDLER_TYPE_NAME);
/*
* Required types not found therefore the kind of pattern considered
* will not be found.
*/
if (googEventsEventHandlerType == null || googDisposableType == null) {
return;
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>String s : order) {
if (eventfulTypes.contains(typeRegistry.getType(s))) {
for (String v : eventizes.get(s)) {
eventfulTypes.add(typeRegistry.getType(v));
}
}
}
}
private JSType maybeReturnDisposedType(Node n, boolean checkDispose) {
/*
* Checks for:
* - Y.registerDisposable(X)
* (Y has to be of type goog.Disposable)
* - X.dispose()
* - goog.dispose(X)
* - X.removeAll() (X is of type goog.events.EventHandler)
* - <array>.property(X) or Y.push(X)
*/
Node first = n.getFirstChild();
if (first == null || !first.isQualifiedName()) {
return null;
}
String property = first.getQualifiedName();
if (property.endsWith(".registerDisposable")) {
/*
* Ensure object is of type disposable
*/
Node base = first.getFirstChild();
JSType baseType = base.getJSType();
if (baseType == null || !isPossiblySubtype(baseType, googDisposableType)) {
return null;
}
return n.getLastChild().getJSType();
}
if (checkDispose) {
if (property.equals("goog.dispose")) {
return n.getLastChild().getJSType();
}
if (property.endsWith(".dispose")) {
/*
* n -> call
* n.firstChild -> "dispose"
* n.firstChild.firstChild -> object
*/
return n.getFirstChild().getFirstChild().getJSType();
}
}
return null;
}
/*
* Compute eventize relationship graph.
*/
private class ComputeEventizeTraversal extends AbstractPostOrderCallback
implements ScopedCallback {
/*
* Keep track of whether in the constructor or disposal scope.
*/
Stack<Boolean> isConstructorStack;
Stack<Boolean> isDisposalStack;
public ComputeEventizeTraversal() {
isConstructorStack = new Stack<Boolean>();
isDisposalStack = new Stack<Boolean>();
eventizes = new HashMap<String, Set<String>>();
}
private Boolean inConstructorScope() {
Preconditions.checkNotNull(isConstructorStack);
if (isDisposalStack.size() > 0) {
return isConstructorStack.peek();
}
return null;
}
private Boolean inDisposalScope() {
Preconditions.checkNotNull(isDisposalStack);
if (isDisposalStack.size() > 0) {
return isDisposalStack.peek();
}
return null;
}
/*
* Filter types not interested in for eventize graph
*/
private boolean collectorFilterType(JSType type) {
if (type == null) {
return true;
}
if (type.isEmptyType() ||
type.isUnknownType() ||
!isPossiblySubtype(type, googDisposableType)) {
return true;
}
return false;
}
/*
* Log that thisType eventizes thatType.
*/
private void addEventize(JSType thisType, JSType thatType) {
if (collectorFilterType(thisType
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>) ||
collectorFilterType(thatType) ||
thisType.isEquivalentTo(thatType)) {
return;
}
String className = thisType.getDisplayName();
if (thatType.isUnionType()) {
UnionType ut = thatType.toMaybeUnionType();
for (JSType type : ut.getAlternates()) {
if (type.isObject()) {
addEventizeClass(className, type);
}
}
} else {
addEventizeClass(className, thatType);
}
}
private void addEventizeClass(String className, JSType thatType) {
String propertyJsTypeName = thatType.getDisplayName();
Set<String> eventize = eventizes.get(propertyJsTypeName);
if (eventize == null) {
eventize = new HashSet<String>();
eventizes.put(propertyJsTypeName, eventize);
}
eventize.add(className);
}
@Override
public void enterScope(NodeTraversal t) {
Node n = t.getScopeRoot();
boolean isConstructor = false;
boolean isInDisposal = false;
String functionName = null;
/*
* Scope entered is a function definition
*/
if (n.isFunction()) {
functionName = NodeUtil.getFunctionName(n);
/*
* Skip anonymous functions
*/
if (functionName != null) {
JSDocInfo jsDocInfo = NodeUtil.getBestJSDocInfo(n);
if (jsDocInfo != null) {
/*
* Record constructor of a type
*/
if (jsDocInfo.isConstructor()) {
isConstructor = true;
/*
* Initialize eventizes relationship
*/
if (t.getScope() != null && t.getScope().getTypeOfThis() != null) {
ObjectType objectType = ObjectType.cast(t.getScope().getTypeOfThis().dereference());
/*
* Eventize due to inheritance
*/
while (objectType != null) {
objectType = objectType.getImplicitPrototype();
if (objectType == null) {
break;
}
if (objectType.getDisplayName().endsWith("prototype")) {
continue;
}
addEventize(compiler.getTypeRegistry().getType(functionName), objectType);
/*
* Don't add transitive eventize edges here, it will be
* taken care of in computeEventful
*/
break;
}
}
}
}
/*
* Indicate within a disposeInternal member
*/
if (functionName.endsWith(".disposeInternal")) {
isInDisposal = true;
}
}
isConstructorStack.push(isConstructor);
isDisposalStack.push(isInDisposal);
} else {
isConstructorStack.push(inConstructorScope());
isDisposalStack.push(inDisposalScope());
}
}
@Override
public void exitScope(NodeTraversal t) {
isConstructorStack.pop();
isDisposalStack.pop();
}
/*
* Is the current node a call to goog.events.unlisten
*/
private void isGoogEventsUnlisten(Node n) {
Preconditions.checkArgument(n.getChildCount() > 3);
Node listener = n.getChildAtIndex(3);
Node object
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>WithListener = n.getChildAtIndex(1);
if (!objectWithListener.isQualifiedName()) {
return;
}
if (listener.isFunction()) {
/*
* Anonymous function
*/
compiler.report(JSError.make(n.getSourceFileName(), n, UNLISTEN_WITH_ANONBOUND));
} else if (listener.isCall()) {
if (!listener.getFirstChild().isQualifiedName()) {
/*
* Anonymous function
*/
compiler.report(JSError.make(n.getSourceFileName(), n, UNLISTEN_WITH_ANONBOUND));
} else if (listener.getFirstChild().getQualifiedName().equals("goog.bind")) {
/*
* Using goog.bind to unlisten
*/
compiler.report(JSError.make(n.getSourceFileName(), n, UNLISTEN_WITH_ANONBOUND));
}
}
}
private void isCalled(NodeTraversal t, Node n) {
Node functionCalled = n.getFirstChild();
if (functionCalled == null ||
!functionCalled.isQualifiedName()) {
return;
}
String functionCalledName = functionCalled.getQualifiedName();
JSType typeOfThis = getTypeOfThisForScope(t);
if (typeOfThis == null) {
return;
}
/*
* Class considered eventful if there is an unlisten call in the
* disposal.
*/
if (functionCalledName.equals("goog.events.unlisten")) {
if (inDisposalScope()) {
eventfulTypes.add(typeOfThis);
}
isGoogEventsUnlisten(n);
}
if (inDisposalScope() &&
functionCalledName.equals("goog.events.removeAll")) {
eventfulTypes.add(typeOfThis);
}
/*
* If member with qualified name gets disposed of when this class
* gets disposed, consider the member type as an eventizer of this
* class.
*/
JSType disposedType = maybeReturnDisposedType(n, inDisposalScope());
if (!collectorFilterType(disposedType)) {
addEventize(getTypeOfThisForScope(t), disposedType);
}
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.CALL:
isCalled(t, n);
break;
default:
break;
}
}
}
private class Traversal extends AbstractPostOrderCallback implements ScopedCallback {
/*
* Checks if the input node correspond to the creation of an eventful object
*/
private boolean createsEventfulObject(Node n) {
Node first = n.getFirstChild();
JSType type = n.getJSType();
if (first == null ||
!first.isQualifiedName() ||
type.isEmptyType() ||
type.isUnknownType()) {
return false;
}
boolean isOfTypeNeedingDisposal = false;
for (JSType disposableType : eventfulTypes) {
if (type.isSubtype(disposableType)) {
isOfTypeNeedingDisposal = true;
break;
}
}
return isOfTypeNeedingDisposal;
}
/*
* This function traverses the current scope to see if a locally
*
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>) {
return;
}
EventfulObjectState e = eventfulObjectMap.get(key);
if (e == null) {
e = new EventfulObjectState();
eventfulObjectMap.put(key, e);
}
e.seen = SeenType.POSSIBLY_DISPOSED;
}
@Override
public void enterScope(NodeTraversal t) {
/*
* Local variables captured in scope are filtered at present.
* LiveVariableAnalysis used to filter such variables.
*/
ControlFlowGraph<Node> cfg = t.getControlFlowGraph();
LiveVariablesAnalysis liveness =
new LiveVariablesAnalysis(cfg, t.getScope(), compiler);
liveness.analyze();
for (Var v : liveness.getEscapedLocals()) {
eventfulObjectDisposed(t, v.getNode());
}
}
@Override
public void exitScope(NodeTraversal t) {
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.ASSIGN:
isAssign(t, n);
break;
case Token.CALL:
isCall(t, n);
break;
case Token.FUNCTION:
isFunction(t, n);
break;
case Token.NEW:
isNew(t, n, parent);
break;
case Token.RETURN:
isReturn(t, n);
break;
default:
break;
}
}
}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.javascript.jscomp.NodeTraversal.ScopedCallback;
import com.google.javascript.jscomp.Scope.Var;
import com.google.javascript.rhino.InputId;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.StaticReference;
import com.google.javascript.rhino.jstype.StaticSourceFile;
import com.google.javascript.rhino.jstype.StaticSymbolTable;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* A helper class for passes that want to access all information about where a
* variable is referenced and declared at once and then make a decision as to
* how it should be handled, possibly inlining, reordering, or generating
* warnings. Callers do this by providing {@link Behavior} and then
* calling {@link #process(Node, Node)}.
*
* @author kushal@google.com (Kushal Dave)
*/
class ReferenceCollectingCallback implements ScopedCallback,
HotSwapCompilerPass,
StaticSymbolTable<Var, ReferenceCollectingCallback.Reference> {
/**
* Maps a given variable to a collection of references to that name. Note that
* Var objects are not stable across multiple traversals (unlike scope root or
* name).
*/
private final Map<Var, ReferenceCollection> referenceMap =
Maps.newHashMap();
/**
* The stack of basic blocks and scopes the current traversal is in.
*/
private final Deque<BasicBlock> blockStack = new ArrayDeque<BasicBlock>();
/**
* Source of behavior at various points in the traversal.
*/
private final Behavior behavior;
/**
* JavaScript compiler to use in traversing.
*/
private final AbstractCompiler compiler;
/**
* Only collect references for filtered variables.
*/
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>
private final Predicate<Var> varFilter;
/**
* Constructor initializes block stack.
*/
ReferenceCollectingCallback(AbstractCompiler compiler, Behavior behavior) {
this(compiler, behavior, Predicates.<Var>alwaysTrue());
}
/**
* Constructor only collects references that match the given variable.
*
* The test for Var equality uses reference equality, so it's necessary to
* inject a scope when you traverse.
*/
ReferenceCollectingCallback(AbstractCompiler compiler, Behavior behavior,
Predicate<Var> varFilter) {
this.compiler = compiler;
this.behavior = behavior;
this.varFilter = varFilter;
}
/**
* Convenience method for running this pass over a tree with this
* class as a callback.
*/
@Override
public void process(Node externs, Node root) {
NodeTraversal.traverseRoots(
compiler, Lists.newArrayList(externs, root), this);
}
/**
* Same as process but only runs on a part of AST associated to one script.
*/
@Override
public void hotSwapScript(Node scriptRoot, Node originalRoot) {
NodeTraversal.traverse(compiler, scriptRoot, this);
}
/**
* Gets the variables that were referenced in this callback.
*/
@Override
public Iterable<Var> getAllSymbols() {
return referenceMap.keySet();
}
@Override
public Scope getScope(Var var) {
return var.scope;
}
/**
* Gets the reference collection for the given variable.
*/
@Override
public ReferenceCollection getReferences(Var v) {
return referenceMap.get(v);
}
/**
* For each node, update the block stack and reference collection
* as appropriate.
*/
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isName()) {
Var v;
if (n.getString().equals("arguments")) {
v = t.getScope().getArgumentsVar();
} else {
v = t.getScope().getVar(n.getString());
}
if (v != null && varFilter.apply(v)) {
addReference(v, new Reference(n, t, blockStack.peek()));
}
}
if (isBlockBoundary(n, parent)) {
blockStack.pop();
}
}
/**
* Updates block stack and invokes any additional behavior.
*/
@Override
public void enterScope(NodeTraversal t) {
Node n = t.getScope().getRootNode();
BasicBlock parent = blockStack.isEmpty() ? null : blockStack.peek();
blockStack.push(new BasicBlock(parent, n));
}
/**
* Updates block stack and invokes any additional behavior.
*/
@Override
public void exitScope(NodeTraversal t) {
blockStack.pop();
if (t.getScope().isGlobal()) {
// Update global scope reference lists when we are done with it.
compiler.updateGlobalVarReferences(referenceMap, t.getScopeRoot());
behavior.afterExitScope(t, compiler.getGlobalVarReferences());
} else {
behavior.afterExitScope(t, new ReferenceMapWrapper(referenceMap));
}
}
/**
* Updates block stack.
*/
@
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>Override
public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
// If node is a new basic block, put on basic block stack
if (isBlockBoundary(n, parent)) {
blockStack.push(new BasicBlock(blockStack.peek(), n));
}
return true;
}
/**
* @return true if this node marks the start of a new basic block
*/
private static boolean isBlockBoundary(Node n, Node parent) {
if (parent != null) {
switch (parent.getType()) {
case Token.DO:
case Token.FOR:
case Token.TRY:
case Token.WHILE:
case Token.WITH:
// NOTE: TRY has up to 3 child blocks:
// TRY
// BLOCK
// BLOCK
// CATCH
// BLOCK
// Note that there is an explicit CATCH token but no explicit
// FINALLY token. For simplicity, we consider each BLOCK
// a separate basic BLOCK.
return true;
case Token.AND:
case Token.HOOK:
case Token.IF:
case Token.OR:
// The first child of a conditional is not a boundary,
// but all the rest of the children are.
return n != parent.getFirstChild();
}
}
return n.isCase();
}
private void addReference(Var v, Reference reference) {
// Create collection if none already
ReferenceCollection referenceInfo = referenceMap.get(v);
if (referenceInfo == null) {
referenceInfo = new ReferenceCollection();
referenceMap.put(v, referenceInfo);
}
// Add this particular reference
referenceInfo.add(reference);
}
interface ReferenceMap {
ReferenceCollection getReferences(Var var);
}
private static class ReferenceMapWrapper implements ReferenceMap {
private final Map<Var, ReferenceCollection> referenceMap;
public ReferenceMapWrapper(Map<Var, ReferenceCollection> referenceMap) {
this.referenceMap = referenceMap;
}
@Override
public ReferenceCollection getReferences(Var var) {
return referenceMap.get(var);
}
}
/**
* Way for callers to add specific behavior during traversal that
* utilizes the built-up reference information.
*/
interface Behavior {
/**
* Called after we finish with a scope.
*/
void afterExitScope(NodeTraversal t, ReferenceMap referenceMap);
}
static final Behavior DO_NOTHING_BEHAVIOR = new Behavior() {
@Override
public void afterExitScope(NodeTraversal t, ReferenceMap referenceMap) {}
};
/**
* A collection of references. Can be subclassed to apply checks or
* store additional state when adding.
*/
static class ReferenceCollection implements Iterable<Reference> {
List<Reference> references = Lists.newArrayList();
@Override
public Iterator<Reference> iterator() {
return references.iterator();
}
void add(Reference reference) {
references.add(reference);
}
/**
* Determines if the variable for this reference collection is
* "well-defined." A variable is well-defined if we can prove at
* compile-time that it's assigned a value before it's used.
*
* Notice that if this function returns false, this doesn't imply
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> that the
* variable is used before it's assigned. It just means that we don't
* have enough information to make a definitive judgment.
*/
protected boolean isWellDefined() {
int size = references.size();
if (size == 0) {
return false;
}
// If this is a declaration that does not instantiate the variable,
// it's not well-defined.
Reference init = getInitializingReference();
if (init == null) {
return false;
}
Preconditions.checkState(references.get(0).isDeclaration());
BasicBlock initBlock = init.getBasicBlock();
for (int i = 1; i < size; i++) {
if (!initBlock.provablyExecutesBefore(
references.get(i).getBasicBlock())) {
return false;
}
}
return true;
}
/**
* Whether the variable is escaped into an inner scope.
*/
boolean isEscaped() {
Scope scope = null;
for (Reference ref : references) {
if (scope == null) {
scope = ref.scope;
} else if (scope != ref.scope) {
return true;
}
}
return false;
}
/**
* @param index The index into the references array to look for an
* assigning declaration.
*
* This is either the declaration if a value is assigned (such as
* "var a = 2", "function a()...", "... catch (a)...").
*/
private boolean isInitializingDeclarationAt(int index) {
Reference maybeInit = references.get(index);
if (maybeInit.isInitializingDeclaration()) {
// This is a declaration that represents the initial value.
// Specifically, var declarations without assignments such as "var a;"
// are not.
return true;
}
return false;
}
/**
* @param index The index into the references array to look for an
* initialized assignment reference. That is, an assignment immediately
* follow a variable declaration that itself does not initialize the
* variable.
*/
private boolean isInitializingAssignmentAt(int index) {
if (index < references.size() && index > 0) {
Reference maybeDecl = references.get(index - 1);
if (maybeDecl.isVarDeclaration()) {
Preconditions.checkState(!maybeDecl.isInitializingDeclaration());
Reference maybeInit = references.get(index);
if (maybeInit.isSimpleAssignmentToName()) {
return true;
}
}
}
return false;
}
/**
* @return The reference that provides the value for the variable at the
* time of the first read, if known, otherwise null.
*
* This is either the variable declaration ("var a = ...") or first
* reference following the declaration if it is an assignment.
*/
Reference getInitializingReference() {
if (isInitializingDeclarationAt(0)) {
return references.get(0);
} else if (isInitializingAssignmentAt(1)) {
return references.get(1);
}
return null;
}
/**
* Constants are allowed to be defined after their first use.
*/
Reference getInitializingReferenceForConstants() {
int size = references.size();
for (int i = 0; i
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> < size; i++) {
if (isInitializingDeclarationAt(i) || isInitializingAssignmentAt(i)) {
return references.get(i);
}
}
return null;
}
/**
* @return Whether the variable is only assigned a value once for its
* lifetime.
*/
boolean isAssignedOnceInLifetime() {
Reference ref = getOneAndOnlyAssignment();
if (ref == null) {
return false;
}
// Make sure this assignment is not in a loop.
for (BasicBlock block = ref.getBasicBlock();
block != null; block = block.getParent()) {
if (block.isFunction) {
break;
} else if (block.isLoop) {
return false;
}
}
return true;
}
/**
* @return The one and only assignment. Returns if there are 0 or 2+
* assignments.
*/
private Reference getOneAndOnlyAssignment() {
Reference assignment = null;
int size = references.size();
for (int i = 0; i < size; i++) {
Reference ref = references.get(i);
if (ref.isLvalue() || ref.isInitializingDeclaration()) {
if (assignment == null) {
assignment = ref;
} else {
return null;
}
}
}
return assignment;
}
/**
* @return Whether the variable is never assigned a value.
*/
boolean isNeverAssigned() {
int size = references.size();
for (int i = 0; i < size; i++) {
Reference ref = references.get(i);
if (ref.isLvalue() || ref.isInitializingDeclaration()) {
return false;
}
}
return true;
}
boolean firstReferenceIsAssigningDeclaration() {
int size = references.size();
if (size > 0 && references.get(0).isInitializingDeclaration()) {
return true;
}
return false;
}
}
/**
* Represents a single declaration or reference to a variable.
*/
static final class Reference implements StaticReference<JSType> {
private static final Set<Integer> DECLARATION_PARENTS =
ImmutableSet.of(Token.VAR, Token.FUNCTION, Token.CATCH);
private final Node nameNode;
private final BasicBlock basicBlock;
private final Scope scope;
private final InputId inputId;
private final StaticSourceFile sourceFile;
Reference(Node nameNode, NodeTraversal t,
BasicBlock basicBlock) {
this(nameNode, basicBlock, t.getScope(), t.getInput().getInputId());
}
// Bleeding functions are weird, because the declaration does
// not appear inside their scope. So they need their own constructor.
static Reference newBleedingFunction(NodeTraversal t,
BasicBlock basicBlock, Node func) {
return new Reference(func.getFirstChild(),
basicBlock, t.getScope(), t.getInput().getInputId());
}
/**
* Creates a variable reference in a given script file name, used in tests.
*
* @return The created reference.
*/
@VisibleForTesting
static Reference createRefForTest(CompilerInput input) {
return new Reference(new Node(Token.NAME), null,
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> null,
input.getInputId());
}
private Reference(Node nameNode,
BasicBlock basicBlock, Scope scope, InputId inputId) {
this.nameNode = nameNode;
this.basicBlock = basicBlock;
this.scope = scope;
this.inputId = inputId;
this.sourceFile = nameNode.getStaticSourceFile();
}
/**
* Makes a copy of the current reference using a new Scope instance.
*/
Reference cloneWithNewScope(Scope newScope) {
return new Reference(nameNode, basicBlock, newScope, inputId);
}
@Override
public Var getSymbol() {
return scope.getVar(nameNode.getString());
}
@Override
public Node getNode() {
return nameNode;
}
public InputId getInputId() {
return inputId;
}
@Override
public StaticSourceFile getSourceFile() {
return sourceFile;
}
boolean isDeclaration() {
Node parent = getParent();
Node grandparent = parent.getParent();
return DECLARATION_PARENTS.contains(parent.getType()) ||
parent.isParamList() &&
grandparent.isFunction();
}
boolean isVarDeclaration() {
return getParent().isVar();
}
boolean isHoistedFunction() {
return NodeUtil.isHoistedFunctionDeclaration(getParent());
}
/**
* Determines whether the variable is initialized at the declaration.
*/
boolean isInitializingDeclaration() {
// VAR is the only type of variable declaration that may not initialize
// its variable. Catch blocks, named functions, and parameters all do.
return isDeclaration() &&
!getParent().isVar() ||
nameNode.getFirstChild() != null;
}
/**
* @return For an assignment, variable declaration, or function declaration
* return the assigned value, otherwise null.
*/
Node getAssignedValue() {
Node parent = getParent();
return (parent.isFunction())
? parent : NodeUtil.getAssignedValue(nameNode);
}
BasicBlock getBasicBlock() {
return basicBlock;
}
Node getParent() {
return getNode().getParent();
}
Node getGrandparent() {
Node parent = getParent();
return parent == null ? null : parent.getParent();
}
private static boolean isLhsOfForInExpression(Node n) {
Node parent = n.getParent();
if (parent.isVar()) {
return isLhsOfForInExpression(parent);
}
return NodeUtil.isForIn(parent) && parent.getFirstChild() == n;
}
boolean isSimpleAssignmentToName() {
Node parent = getParent();
return parent.isAssign()
&& parent.getFirstChild() == nameNode;
}
boolean isLvalue() {
Node parent = getParent();
int parentType = parent.getType();
return (parentType == Token.VAR && nameNode.getFirstChild() != null)
|| parentType == Token.INC
|| parentType == Token.DEC
|| (NodeUtil.isAssignmentOp(parent)
&& parent.getFirstChild() == nameNode)
|| isLhsOfForInExpression(nameNode);
}
Scope getScope() {
return scope;
}
}
/**
* Represents a section of code that is uninterrupted by control
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> structures
* (conditional or iterative logic).
*/
static final class BasicBlock {
private final BasicBlock parent;
/**
* Determines whether the block may not be part of the normal control flow,
* but instead "hoisted" to the top of the scope.
*/
private final boolean isHoisted;
/**
* Whether this block denotes a function scope.
*/
private final boolean isFunction;
/**
* Whether this block denotes a loop.
*/
private final boolean isLoop;
/**
* Creates a new block.
* @param parent The containing block.
* @param root The root node of the block.
*/
BasicBlock(BasicBlock parent, Node root) {
this.parent = parent;
// only named functions may be hoisted.
this.isHoisted = NodeUtil.isHoistedFunctionDeclaration(root);
this.isFunction = root.isFunction();
if (root.getParent() != null) {
int pType = root.getParent().getType();
this.isLoop = pType == Token.DO ||
pType == Token.WHILE ||
pType == Token.FOR;
} else {
this.isLoop = false;
}
}
BasicBlock getParent() {
return parent;
}
/**
* Determines whether this block is equivalent to the very first block that
* is created when reference collection traversal enters global scope. Note
* that when traversing a single script in a hot-swap fashion a new instance
* of {@code BasicBlock} is created.
*
* @return true if this is global scope block.
*/
boolean isGlobalScopeBlock() {
return getParent() == null;
}
/**
* Determines whether this block is guaranteed to begin executing before
* the given block does.
*/
boolean provablyExecutesBefore(BasicBlock thatBlock) {
// If thatBlock is a descendant of this block, and there are no hoisted
// blocks between them, then this block must start before thatBlock.
BasicBlock currentBlock;
for (currentBlock = thatBlock;
currentBlock != null && currentBlock != this;
currentBlock = currentBlock.getParent()) {
if (currentBlock.isHoisted) {
return false;
}
}
if (currentBlock == this) {
return true;
}
if (isGlobalScopeBlock() && thatBlock.isGlobalScopeBlock()) {
return true;
}
return false;
}
}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>Root, false);
}
/** Main entry point of this phase for testing code. */
public Scope processForTesting(Node externsRoot, Node jsRoot) {
Preconditions.checkState(scopeCreator == null);
Preconditions.checkState(topScope == null);
Preconditions.checkState(jsRoot.getParent() != null);
Node externsAndJsRoot = jsRoot.getParent();
scopeCreator = new MemoizedScopeCreator(new TypedScopeCreator(compiler));
topScope = scopeCreator.createScope(externsAndJsRoot, null);
TypeInferencePass inference = new TypeInferencePass(compiler,
reverseInterpreter, topScope, scopeCreator);
inference.process(externsRoot, jsRoot);
process(externsRoot, jsRoot);
return topScope;
}
public void check(Node node, boolean externs) {
Preconditions.checkNotNull(node);
NodeTraversal t = new NodeTraversal(compiler, this, scopeCreator);
inExterns = externs;
t.traverseWithScope(node, topScope);
if (externs) {
inferJSDocInfo.process(node, null);
} else {
inferJSDocInfo.process(null, node);
}
}
private void checkNoTypeCheckSection(Node n, boolean enterSection) {
switch (n.getType()) {
case Token.SCRIPT:
case Token.BLOCK:
case Token.VAR:
case Token.FUNCTION:
case Token.ASSIGN:
JSDocInfo info = n.getJSDocInfo();
if (info != null && info.isNoTypeCheck()) {
if (enterSection) {
noTypeCheckSection++;
} else {
noTypeCheckSection--;
}
}
validator.setShouldReport(noTypeCheckSection == 0);
break;
}
}
private void report(NodeTraversal t, Node n, DiagnosticType diagnosticType,
String... arguments) {
if (noTypeCheckSection == 0) {
t.report(n, diagnosticType, arguments);
}
}
@Override
public boolean shouldTraverse(
NodeTraversal t, Node n, Node parent) {
checkNoTypeCheckSection(n, true);
switch (n.getType()) {
case Token.FUNCTION:
// normal type checking
final Scope outerScope = t.getScope();
final String functionPrivateName = n.getFirstChild().getString();
if (functionPrivateName != null && functionPrivateName.length() > 0 &&
outerScope.isDeclared(functionPrivateName, false) &&
// Ideally, we would want to check whether the type in the scope
// differs from the type being defined, but then the extern
// redeclarations of built-in types generates spurious warnings.
!(outerScope.getVar(
functionPrivateName).getType() instanceof FunctionType)) {
report(t, n, FUNCTION_MASKS_VARIABLE, functionPrivateName);
}
// TODO(user): Only traverse the function's body. The function's
// name and arguments are traversed by the scope creator, and ideally
// should not be traversed by the type checker.
break;
}
return true;
}
/**
* This is the meat of the type checking. It is basically one big
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> switch,
* with each case representing one type of parse tree node. The individual
* cases are usually pretty straightforward.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
* @param parent The parent of the node n.
*/
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
JSType childType;
JSType leftType, rightType;
Node left, right;
// To be explicitly set to false if the node is not typeable.
boolean typeable = true;
switch (n.getType()) {
case Token.CAST:
Node expr = n.getFirstChild();
JSType exprType = getJSType(expr);
JSType castType = getJSType(n);
// TODO(johnlenz): determine if we can limit object literals in some
// way.
if (!expr.isObjectLit()) {
validator.expectCanCast(t, n, castType, exprType);
}
ensureTyped(t, n, castType);
if (castType.isSubtype(exprType) || expr.isObjectLit()) {
expr.setJSType(castType);
}
break;
case Token.NAME:
typeable = visitName(t, n, parent);
break;
case Token.PARAM_LIST:
typeable = false;
break;
case Token.COMMA:
ensureTyped(t, n, getJSType(n.getLastChild()));
break;
case Token.TRUE:
case Token.FALSE:
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.THIS:
ensureTyped(t, n, t.getScope().getTypeOfThis());
break;
case Token.NULL:
ensureTyped(t, n, NULL_TYPE);
break;
case Token.NUMBER:
ensureTyped(t, n, NUMBER_TYPE);
break;
case Token.STRING:
ensureTyped(t, n, STRING_TYPE);
break;
case Token.STRING_KEY:
typeable = false;
break;
case Token.GETTER_DEF:
case Token.SETTER_DEF:
// Object literal keys are handled with OBJECTLIT
break;
case Token.ARRAYLIT:
ensureTyped(t, n, ARRAY_TYPE);
break;
case Token.REGEXP:
ensureTyped(t, n, REGEXP_TYPE);
break;
case Token.GETPROP:
visitGetProp(t, n, parent);
typeable = !(parent.isAssign() &&
parent.getFirstChild() == n);
break;
case Token.GETELEM:
visitGetElem(t, n);
// The type of GETELEM is always unknown, so no point counting that.
// If that unknown leaks elsewhere (say by an assignment to another
// variable), then it will be counted.
typeable = false;
break;
case Token.VAR:
visitVar(t, n);
typeable = false;
break;
case Token.NEW:
visitNew(t, n);
break;
case Token.CALL:
visitCall(t,
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> n);
typeable = !parent.isExprResult();
break;
case Token.RETURN:
visitReturn(t, n);
typeable = false;
break;
case Token.DEC:
case Token.INC:
left = n.getFirstChild();
checkPropCreation(t, left);
validator.expectNumber(t, left, getJSType(left), "increment/decrement");
ensureTyped(t, n, NUMBER_TYPE);
break;
case Token.NOT:
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.VOID:
ensureTyped(t, n, VOID_TYPE);
break;
case Token.TYPEOF:
ensureTyped(t, n, STRING_TYPE);
break;
case Token.BITNOT:
childType = getJSType(n.getFirstChild());
if (!childType.matchesInt32Context()) {
report(t, n, BIT_OPERATION, NodeUtil.opToStr(n.getType()),
childType.toString());
}
ensureTyped(t, n, NUMBER_TYPE);
break;
case Token.POS:
case Token.NEG:
left = n.getFirstChild();
validator.expectNumber(t, left, getJSType(left), "sign operator");
ensureTyped(t, n, NUMBER_TYPE);
break;
case Token.EQ:
case Token.NE:
case Token.SHEQ:
case Token.SHNE: {
left = n.getFirstChild();
right = n.getLastChild();
if (left.isTypeOf()) {
if (right.isString()) {
checkTypeofString(t, right, right.getString());
}
} else if (right.isTypeOf() && left.isString()) {
checkTypeofString(t, left, left.getString());
}
leftType = getJSType(left);
rightType = getJSType(right);
// We do not want to warn about explicit comparisons to VOID. People
// often do this if they think their type annotations screwed up.
//
// We do want to warn about cases where people compare things like
// (Array|null) == (Function|null)
// because it probably means they screwed up.
//
// This heuristic here is not perfect, but should catch cases we
// care about without too many false negatives.
JSType leftTypeRestricted = leftType.restrictByNotNullOrUndefined();
JSType rightTypeRestricted = rightType.restrictByNotNullOrUndefined();
TernaryValue result = TernaryValue.UNKNOWN;
if (n.getType() == Token.EQ || n.getType() == Token.NE) {
result = leftTypeRestricted.testForEquality(rightTypeRestricted);
if (n.isNE()) {
result = result.not();
}
} else {
// SHEQ or SHNE
if (!leftTypeRestricted.canTestForShallowEqualityWith(
rightTypeRestricted)) {
result = n.getType() == Token.SHEQ ?
TernaryValue.FALSE : TernaryValue.TRUE;
}
}
if (result != TernaryValue.UNKNOWN) {
report(t, n, DETERMINISTIC_TEST, leftType.toString(),
rightType
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>BITAND:
case Token.SUB:
case Token.ADD:
case Token.MUL:
visitBinaryOperator(n.getType(), t, n);
break;
case Token.DELPROP:
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.CASE:
JSType switchType = getJSType(parent.getFirstChild());
JSType caseType = getJSType(n.getFirstChild());
validator.expectSwitchMatchesCase(t, n, switchType, caseType);
typeable = false;
break;
case Token.WITH: {
Node child = n.getFirstChild();
childType = getJSType(child);
validator.expectObject(t, child, childType, "with requires an object");
typeable = false;
break;
}
case Token.FUNCTION:
visitFunction(t, n);
break;
// These nodes have no interesting type behavior.
case Token.LABEL:
case Token.LABEL_NAME:
case Token.SWITCH:
case Token.BREAK:
case Token.CATCH:
case Token.TRY:
case Token.SCRIPT:
case Token.EXPR_RESULT:
case Token.BLOCK:
case Token.EMPTY:
case Token.DEFAULT_CASE:
case Token.CONTINUE:
case Token.DEBUGGER:
case Token.THROW:
typeable = false;
break;
// These nodes require data flow analysis.
case Token.DO:
case Token.IF:
case Token.WHILE:
typeable = false;
break;
case Token.FOR:
if (NodeUtil.isForIn(n)) {
Node obj = n.getChildAtIndex(1);
if (getJSType(obj).isStruct()) {
report(t, obj, IN_USED_WITH_STRUCT);
}
}
typeable = false;
break;
// These nodes are typed during the type inference.
case Token.AND:
case Token.HOOK:
case Token.OBJECTLIT:
case Token.OR:
if (n.getJSType() != null) { // If we didn't run type inference.
ensureTyped(t, n);
} else {
// If this is an enum, then give that type to the objectlit as well.
if ((n.isObjectLit())
&& (parent.getJSType() instanceof EnumType)) {
ensureTyped(t, n, parent.getJSType());
} else {
ensureTyped(t, n);
}
}
if (n.isObjectLit()) {
JSType typ = getJSType(n);
for (Node key : n.children()) {
visitObjLitKey(t, key, n, typ);
}
}
break;
default:
report(t, n, UNEXPECTED_TOKEN, Token.name(n.getType()));
ensureTyped(t, n);
break;
}
// Don't count externs since the user's code may not even use that part.
typeable = typeable && !inExterns;
if (typeable) {
doPercentTypedAccounting(t, n);
}
checkNoTypeCheckSection(n, false);
}
private void checkTypeofString(NodeTraversal t, Node n, String s
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>) {
if (!(s.equals("number") || s.equals("string") || s.equals("boolean") ||
s.equals("undefined") || s.equals("function") ||
s.equals("object") || s.equals("unknown"))) {
validator.expectValidTypeofName(t, n, s);
}
}
/**
* Counts the given node in the typed statistics.
* @param n a node that should be typed
*/
private void doPercentTypedAccounting(NodeTraversal t, Node n) {
JSType type = n.getJSType();
if (type == null) {
nullCount++;
} else if (type.isUnknownType()) {
if (reportUnknownTypes) {
compiler.report(t.makeError(n, UNKNOWN_EXPR_TYPE));
}
unknownCount++;
} else {
typedCount++;
}
}
/**
* Visits an assignment <code>lvalue = rvalue</code>. If the
* <code>lvalue</code> is a prototype modification, we change the schema
* of the object type it is referring to.
* @param t the traversal
* @param assign the assign node
* (<code>assign.isAssign()</code> is an implicit invariant)
*/
private void visitAssign(NodeTraversal t, Node assign) {
JSDocInfo info = assign.getJSDocInfo();
Node lvalue = assign.getFirstChild();
Node rvalue = assign.getLastChild();
// Check property sets to 'object.property' when 'object' is known.
if (lvalue.isGetProp()) {
Node object = lvalue.getFirstChild();
JSType objectJsType = getJSType(object);
Node property = lvalue.getLastChild();
String pname = property.getString();
// the first name in this getprop refers to an interface
// we perform checks in addition to the ones below
if (object.isGetProp()) {
JSType jsType = getJSType(object.getFirstChild());
if (jsType.isInterface() &&
object.getLastChild().getString().equals("prototype")) {
visitInterfaceGetprop(t, assign, object, pname, lvalue, rvalue);
}
}
checkEnumAlias(t, info, rvalue);
checkPropCreation(t, lvalue);
// Prototype assignments are special, because they actually affect
// the definition of a class. These are mostly validated
// during TypedScopeCreator, and we only look for the "dumb" cases here.
// object.prototype = ...;
if (pname.equals("prototype")) {
if (objectJsType != null && objectJsType.isFunctionType()) {
FunctionType functionType = objectJsType.toMaybeFunctionType();
if (functionType.isConstructor()) {
JSType rvalueType = rvalue.getJSType();
validator.expectObject(t, rvalue, rvalueType,
OVERRIDING_PROTOTYPE_WITH_NON_OBJECT);
// Only assign structs to the prototype of a @struct constructor
if (functionType.makesStructs() && !rvalueType.isStruct()) {
String funName = functionType.getTypeOfThis().toString();
compiler.report(t.makeError(assign,
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> CONFLICTING_SHAPE_TYPE,
"struct", funName));
}
return;
}
}
}
// The generic checks for 'object.property' when 'object' is known,
// and 'property' is declared on it.
// object.property = ...;
ObjectType type = ObjectType.cast(
objectJsType.restrictByNotNullOrUndefined());
if (type != null) {
if (type.hasProperty(pname) &&
!type.isPropertyTypeInferred(pname) &&
!propertyIsImplicitCast(type, pname)) {
JSType expectedType = type.getPropertyType(pname);
if (!expectedType.isUnknownType()) {
validator.expectCanAssignToPropertyOf(
t, assign, getJSType(rvalue),
expectedType, object, pname);
checkPropertyInheritanceOnGetpropAssign(
t, assign, object, pname, info, expectedType);
return;
}
}
}
// If we couldn't get the property type with normal object property
// lookups, then check inheritance anyway with the unknown type.
checkPropertyInheritanceOnGetpropAssign(
t, assign, object, pname, info, getNativeType(UNKNOWN_TYPE));
}
// Check qualified name sets to 'object' and 'object.property'.
// This can sometimes handle cases when the type of 'object' is not known.
// e.g.,
// var obj = createUnknownType();
// /** @type {number} */ obj.foo = true;
JSType leftType = getJSType(lvalue);
if (lvalue.isQualifiedName()) {
// variable with inferred type case
Var var = t.getScope().getVar(lvalue.getQualifiedName());
if (var != null) {
if (var.isTypeInferred()) {
return;
}
if (NodeUtil.getRootOfQualifiedName(lvalue).isThis() &&
t.getScope() != var.getScope()) {
// Don't look at "this.foo" variables from other scopes.
return;
}
if (var.getType() != null) {
leftType = var.getType();
}
}
}
// Fall through case for arbitrary LHS and arbitrary RHS.
Node rightChild = assign.getLastChild();
JSType rightType = getJSType(rightChild);
if (validator.expectCanAssignTo(
t, assign, rightType, leftType, "assignment")) {
ensureTyped(t, assign, rightType);
} else {
ensureTyped(t, assign);
}
}
/**
* After a struct object is created, we can't add new properties to it, with
* one exception. We allow creation of "static" properties like
* Foo.prototype.bar = baz;
* where Foo.prototype is a struct, if the assignment happens at the top level
* and the constructor Foo is defined in the same file.
*/
private void checkPropCreation(NodeTraversal t, Node lvalue) {
if (lvalue.isGetProp()) {
Node obj = lvalue.getFirstChild();
Node prop = lvalue.getLastChild();
JSType objType = getJSType(obj);
String pname = prop.getString();
if (!objType.isStruct()
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> || objType.hasProperty(pname)) {
return;
}
Scope s = t.getScope();
if (obj.isThis() && getJSType(s.getRootNode()).isConstructor()) {
return;
}
// Prop created outside ctor, check that it's a static prop
Node assgnExp = lvalue.getParent();
Node assgnStm = assgnExp.getParent();
if (objType instanceof ObjectType &&
s.isGlobal() &&
NodeUtil.isPrototypePropertyDeclaration(assgnStm)) {
ObjectType instance =
objType.toObjectType().getOwnerFunction().getInstanceType();
String file = lvalue.getSourceFileName();
Node ctor = instance.getConstructor().getSource();
if (ctor != null && ctor.getSourceFileName().equals(file)) {
JSType rvalueType = assgnExp.getLastChild().getJSType();
instance.defineInferredProperty(pname, rvalueType, lvalue);
return;
}
}
report(t, prop, ILLEGAL_PROPERTY_CREATION);
}
}
private void checkPropertyInheritanceOnGetpropAssign(
NodeTraversal t, Node assign, Node object, String property,
JSDocInfo info, JSType propertyType) {
// Inheritance checks for prototype properties.
//
// TODO(nicksantos): This isn't the right place to do this check. We
// really want to do this when we're looking at the constructor.
// We'd find all its properties and make sure they followed inheritance
// rules, like we currently do for @implements to make sure
// all the methods are implemented.
//
// As-is, this misses many other ways to override a property.
//
// object.prototype.property = ...;
if (object.isGetProp()) {
Node object2 = object.getFirstChild();
String property2 = NodeUtil.getStringValue(object.getLastChild());
if ("prototype".equals(property2)) {
JSType jsType = getJSType(object2);
if (jsType.isFunctionType()) {
FunctionType functionType = jsType.toMaybeFunctionType();
if (functionType.isConstructor() || functionType.isInterface()) {
checkDeclaredPropertyInheritance(
t, assign, functionType, property, info, propertyType);
}
}
}
}
}
/**
* Visits an object literal field definition <code>key : value</code>.
*
* If the <code>lvalue</code> is a prototype modification, we change the
* schema of the object type it is referring to.
*
* @param t the traversal
* @param key the assign node
*/
private void visitObjLitKey(
NodeTraversal t, Node key, Node objlit, JSType litType) {
// Do not validate object lit value types in externs. We don't really care,
// and it makes it easier to generate externs.
if (objlit.isFromExterns()) {
ensureTyped(t, key);
return;
}
// Structs must have unquoted keys and dicts must have quoted keys
if (litType.isStruct() && key.isQuotedString()) {
report(t, key, ILLEGAL_OBJLIT_KEY, "
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>SuperClassConstructor();
boolean superClassHasProperty = superClass != null &&
superClass.getInstanceType().hasProperty(propertyName);
boolean superClassHasDeclaredProperty = superClass != null &&
superClass.getInstanceType().isPropertyTypeDeclared(propertyName);
// For interface
boolean superInterfaceHasProperty = false;
boolean superInterfaceHasDeclaredProperty = false;
if (ctorType.isInterface()) {
for (ObjectType interfaceType : ctorType.getExtendedInterfaces()) {
superInterfaceHasProperty =
superInterfaceHasProperty ||
interfaceType.hasProperty(propertyName);
superInterfaceHasDeclaredProperty =
superInterfaceHasDeclaredProperty ||
interfaceType.isPropertyTypeDeclared(propertyName);
}
}
boolean declaredOverride = info != null && info.isOverride();
boolean foundInterfaceProperty = false;
if (ctorType.isConstructor()) {
for (JSType implementedInterface :
ctorType.getAllImplementedInterfaces()) {
if (implementedInterface.isUnknownType() ||
implementedInterface.isEmptyType()) {
continue;
}
FunctionType interfaceType =
implementedInterface.toObjectType().getConstructor();
Preconditions.checkNotNull(interfaceType);
boolean interfaceHasProperty =
interfaceType.getPrototype().hasProperty(propertyName);
foundInterfaceProperty = foundInterfaceProperty ||
interfaceHasProperty;
if (reportMissingOverride.isOn()
&& !declaredOverride
&& interfaceHasProperty) {
// @override not present, but the property does override an interface
// property
compiler.report(t.makeError(n, reportMissingOverride,
HIDDEN_INTERFACE_PROPERTY, propertyName,
interfaceType.getTopMostDefiningType(propertyName).toString()));
}
}
}
if (!declaredOverride
&& !superClassHasProperty
&& !superInterfaceHasProperty) {
// nothing to do here, it's just a plain new property
return;
}
ObjectType topInstanceType = superClassHasDeclaredProperty ?
superClass.getTopMostDefiningType(propertyName) : null;
boolean declaredLocally =
ctorType.isConstructor() &&
(ctorType.getPrototype().hasOwnProperty(propertyName) ||
ctorType.getInstanceType().hasOwnProperty(propertyName));
if (reportMissingOverride.isOn()
&& !declaredOverride
&& superClassHasDeclaredProperty
&& declaredLocally) {
// @override not present, but the property does override a superclass
// property
compiler.report(t.makeError(n, reportMissingOverride,
HIDDEN_SUPERCLASS_PROPERTY, propertyName,
topInstanceType.toString()));
}
// @override is present and we have to check that it is ok
if (superClassHasDeclaredProperty) {
// there is a superclass implementation
JSType superClassPropType =
superClass.getInstanceType().getPropertyType(propertyName);
TemplateTypeMap ctorTypeMap =
ctorType.getTypeOfThis().getTemplateTypeMap();
if (!ctorTypeMap.isEmpty()) {
superClassPropType = superClassPropType.visit(
new TemplateTypeMapReplacer(typeRegistry, ctorTypeMap));
}
if (!propertyType.isSubtype(superClassPropType)) {
compiler.report(
t.makeError(n, HIDDEN_SUPERCLASS_PROPERTY_MISMATCH,
propertyName, topInstanceType.toString(),
superClassPropType.toString(), propertyType.toString()));
}
} else if (superInterfaceHasDeclaredProperty) {
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> // there is an super interface property
for (ObjectType interfaceType : ctorType.getExtendedInterfaces()) {
if (interfaceType.hasProperty(propertyName)) {
JSType superPropertyType =
interfaceType.getPropertyType(propertyName);
if (!propertyType.isSubtype(superPropertyType)) {
topInstanceType = interfaceType.getConstructor().
getTopMostDefiningType(propertyName);
compiler.report(
t.makeError(n, HIDDEN_SUPERCLASS_PROPERTY_MISMATCH,
propertyName, topInstanceType.toString(),
superPropertyType.toString(),
propertyType.toString()));
}
}
}
} else if (!foundInterfaceProperty
&& !superClassHasProperty
&& !superInterfaceHasProperty) {
// there is no superclass nor interface implementation
compiler.report(
t.makeError(n, UNKNOWN_OVERRIDE,
propertyName, ctorType.getInstanceType().toString()));
}
}
/**
* Given a constructor or an interface type, find out whether the unknown
* type is a supertype of the current type.
*/
private static boolean hasUnknownOrEmptySupertype(FunctionType ctor) {
Preconditions.checkArgument(ctor.isConstructor() || ctor.isInterface());
Preconditions.checkArgument(!ctor.isUnknownType());
// The type system should notice inheritance cycles on its own
// and break the cycle.
while (true) {
ObjectType maybeSuperInstanceType =
ctor.getPrototype().getImplicitPrototype();
if (maybeSuperInstanceType == null) {
return false;
}
if (maybeSuperInstanceType.isUnknownType() ||
maybeSuperInstanceType.isEmptyType()) {
return true;
}
ctor = maybeSuperInstanceType.getConstructor();
if (ctor == null) {
return false;
}
Preconditions.checkState(ctor.isConstructor() || ctor.isInterface());
}
}
/**
* Visits an ASSIGN node for cases such as
* <pre>
* interface.property2.property = ...;
* </pre>
*/
private void visitInterfaceGetprop(NodeTraversal t, Node assign, Node object,
String property, Node lvalue, Node rvalue) {
JSType rvalueType = getJSType(rvalue);
// Only 2 values are allowed for methods:
// goog.abstractMethod
// function () {};
// or for properties, no assignment such as:
// InterfaceFoo.prototype.foobar;
String abstractMethodName =
compiler.getCodingConvention().getAbstractMethodName();
if (!rvalueType.isFunctionType()) {
// This is bad i18n style but we don't localize our compiler errors.
String abstractMethodMessage = (abstractMethodName != null)
? ", or " + abstractMethodName
: "";
compiler.report(
t.makeError(object, INVALID_INTERFACE_MEMBER_DECLARATION,
abstractMethodMessage));
}
if (assign.getLastChild().isFunction()
&& !NodeUtil.isEmptyBlock(assign.getLastChild().getLastChild())) {
compiler.report(
t.makeError(object, INTERFACE_FUNCTION_NOT_EMPTY,
abstractMethodName));
}
}
/**
* Visits a NAME node.
*
* @param t The node traversal object that supplies context, such as the
* scope
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> chain to use in name lookups as well as error reporting.
* @param n The node being visited.
* @param parent The parent of the node n.
* @return whether the node is typeable or not
*/
boolean visitName(NodeTraversal t, Node n, Node parent) {
// At this stage, we need to determine whether this is a leaf
// node in an expression (which therefore needs to have a type
// assigned for it) versus some other decorative node that we
// can safely ignore. Function names, arguments (children of LP nodes) and
// variable declarations are ignored.
// TODO(user): remove this short-circuiting in favor of a
// pre order traversal of the FUNCTION, CATCH, LP and VAR nodes.
int parentNodeType = parent.getType();
if (parentNodeType == Token.FUNCTION ||
parentNodeType == Token.CATCH ||
parentNodeType == Token.PARAM_LIST ||
parentNodeType == Token.VAR) {
return false;
}
JSType type = n.getJSType();
if (type == null) {
type = getNativeType(UNKNOWN_TYPE);
Var var = t.getScope().getVar(n.getString());
if (var != null) {
JSType varType = var.getType();
if (varType != null) {
type = varType;
}
}
}
ensureTyped(t, n, type);
return true;
}
/**
* Visits a GETPROP node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
* @param parent The parent of <code>n</code>
*/
private void visitGetProp(NodeTraversal t, Node n, Node parent) {
// obj.prop or obj.method()
// Lots of types can appear on the left, a call to a void function can
// never be on the left. getPropertyType will decide what is acceptable
// and what isn't.
Node property = n.getLastChild();
Node objNode = n.getFirstChild();
JSType childType = getJSType(objNode);
if (childType.isDict()) {
report(t, property, TypeValidator.ILLEGAL_PROPERTY_ACCESS, "'.'", "dict");
} else if (validator.expectNotNullOrUndefined(t, n, childType,
"No properties on this expression", getNativeType(OBJECT_TYPE))) {
checkPropertyAccess(childType, property.getString(), t, n);
}
ensureTyped(t, n);
}
/**
* Emit a warning if we can prove that a property cannot possibly be
* defined on an object. Note the difference between JS and a strictly
* statically typed language: we're checking if the property
* *cannot be defined*, whereas a java compiler would check if the
* property *can be undefined*.
*/
private void checkPropertyAccess(JSType childType, String propName,
NodeTraversal t, Node n) {
// If the property type is unknown, check the object type to see if it
// can ever be defined. We explicitly exclude CHECKED_UNKNOWN (for
// properties where we
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>
if (pair.distance == shortest) {
if (bestSoFar != null &&
pair.suggestion.compareToIgnoreCase(bestSoFar) > 0) {
continue;
}
}
shortest = pair.distance;
bestSoFar = pair.suggestion;
}
}
}
}
if (bestSoFar != null) {
return new SuggestionPair(bestSoFar, shortest);
}
return null;
}
/**
* Determines whether this node is testing for the existence of a property.
* If true, we will not emit warnings about a missing property.
*
* @param getProp The GETPROP being tested.
*/
private boolean isPropertyTest(Node getProp) {
Node parent = getProp.getParent();
switch (parent.getType()) {
case Token.CALL:
return parent.getFirstChild() != getProp &&
compiler.getCodingConvention().isPropertyTestFunction(parent);
case Token.IF:
case Token.WHILE:
case Token.DO:
case Token.FOR:
return NodeUtil.getConditionExpression(parent) == getProp;
case Token.INSTANCEOF:
case Token.TYPEOF:
return true;
case Token.AND:
case Token.HOOK:
return parent.getFirstChild() == getProp;
case Token.NOT:
return parent.getParent().isOr() &&
parent.getParent().getFirstChild() == parent;
case Token.CAST:
return isPropertyTest(parent);
}
return false;
}
/**
* Visits a GETELEM node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
*/
private void visitGetElem(NodeTraversal t, Node n) {
validator.expectIndexMatch(
t, n, getJSType(n.getFirstChild()), getJSType(n.getLastChild()));
ensureTyped(t, n);
}
/**
* Visits a VAR node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
*/
private void visitVar(NodeTraversal t, Node n) {
// TODO(nicksantos): Fix this so that the doc info always shows up
// on the NAME node. We probably want to wait for the parser
// merge to fix this.
JSDocInfo varInfo = n.hasOneChild() ? n.getJSDocInfo() : null;
for (Node name : n.children()) {
Node value = name.getFirstChild();
// A null var would indicate a bug in the scope creation logic.
Var var = t.getScope().getVar(name.getString());
if (value != null) {
JSType valueType = getJSType(value);
JSType nameType = var.getType();
nameType = (nameType == null) ? getNativeType(UNKNOWN_TYPE) : nameType;
JSDocInfo info = name.getJSDocInfo();
if (info == null) {
info = varInfo;
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> checkEnumAlias(t, info, value);
if (var.isTypeInferred()) {
ensureTyped(t, name, valueType);
} else {
validator.expectCanAssignTo(
t, value, valueType, nameType, "initializing variable");
}
}
}
}
/**
* Visits a NEW node.
*/
private void visitNew(NodeTraversal t, Node n) {
Node constructor = n.getFirstChild();
JSType type = getJSType(constructor).restrictByNotNullOrUndefined();
if (type.isConstructor() || type.isEmptyType() || type.isUnknownType()) {
FunctionType fnType = type.toMaybeFunctionType();
if (fnType != null && fnType.hasInstanceType()) {
visitParameterList(t, n, fnType);
ensureTyped(t, n, fnType.getInstanceType());
} else {
ensureTyped(t, n);
}
} else {
report(t, n, NOT_A_CONSTRUCTOR);
ensureTyped(t, n);
}
}
/**
* Check whether there's any property conflict for for a particular super
* interface
* @param t The node traversal object that supplies context
* @param n The node being visited
* @param functionName The function name being checked
* @param properties The property names in the super interfaces that have
* been visited
* @param currentProperties The property names in the super interface
* that have been visited
* @param interfaceType The super interface that is being visited
*/
private void checkInterfaceConflictProperties(NodeTraversal t, Node n,
String functionName, HashMap<String, ObjectType> properties,
HashMap<String, ObjectType> currentProperties,
ObjectType interfaceType) {
ObjectType implicitProto = interfaceType.getImplicitPrototype();
Set<String> currentPropertyNames;
if (implicitProto == null) {
// This can be the case if interfaceType is proxy to a non-existent
// object (which is a bad type annotation, but shouldn't crash).
currentPropertyNames = ImmutableSet.of();
} else {
currentPropertyNames = implicitProto.getOwnPropertyNames();
}
for (String name : currentPropertyNames) {
ObjectType oType = properties.get(name);
if (oType != null) {
if (!interfaceType.getPropertyType(name).isEquivalentTo(
oType.getPropertyType(name))) {
compiler.report(
t.makeError(n, INCOMPATIBLE_EXTENDED_PROPERTY_TYPE,
functionName, name, oType.toString(),
interfaceType.toString()));
}
}
currentProperties.put(name, interfaceType);
}
for (ObjectType iType : interfaceType.getCtorExtendedInterfaces()) {
checkInterfaceConflictProperties(t, n, functionName, properties,
currentProperties, iType);
}
}
/**
* Visits a {@link Token#FUNCTION} node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType functionType = JSType.toMaybeFunctionType(
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>n.getJSType());
String functionPrivateName = n.getFirstChild().getString();
if (functionType.isConstructor()) {
FunctionType baseConstructor = functionType.getSuperClassConstructor();
if (baseConstructor != getNativeType(OBJECT_FUNCTION_TYPE) &&
baseConstructor != null &&
baseConstructor.isInterface()) {
compiler.report(
t.makeError(n, CONFLICTING_EXTENDED_TYPE,
"constructor", functionPrivateName));
} else {
if (baseConstructor != getNativeType(OBJECT_FUNCTION_TYPE)) {
ObjectType proto = functionType.getPrototype();
if (functionType.makesStructs() && !proto.isStruct()) {
compiler.report(t.makeError(n, CONFLICTING_SHAPE_TYPE,
"struct", functionPrivateName));
} else if (functionType.makesDicts() && !proto.isDict()) {
compiler.report(t.makeError(n, CONFLICTING_SHAPE_TYPE,
"dict", functionPrivateName));
}
}
// All interfaces are properly implemented by a class
for (JSType baseInterface : functionType.getImplementedInterfaces()) {
boolean badImplementedType = false;
ObjectType baseInterfaceObj = ObjectType.cast(baseInterface);
if (baseInterfaceObj != null) {
FunctionType interfaceConstructor =
baseInterfaceObj.getConstructor();
if (interfaceConstructor != null &&
!interfaceConstructor.isInterface()) {
badImplementedType = true;
}
} else {
badImplementedType = true;
}
if (badImplementedType) {
report(t, n, BAD_IMPLEMENTED_TYPE, functionPrivateName);
}
}
// check properties
validator.expectAllInterfaceProperties(t, n, functionType);
}
} else if (functionType.isInterface()) {
// Interface must extend only interfaces
for (ObjectType extInterface : functionType.getExtendedInterfaces()) {
if (extInterface.getConstructor() != null
&& !extInterface.getConstructor().isInterface()) {
compiler.report(
t.makeError(n, CONFLICTING_EXTENDED_TYPE,
"interface", functionPrivateName));
}
}
// Check whether the extended interfaces have any conflicts
if (functionType.getExtendedInterfacesCount() > 1) {
// Only check when extending more than one interfaces
HashMap<String, ObjectType> properties
= new HashMap<String, ObjectType>();
HashMap<String, ObjectType> currentProperties
= new HashMap<String, ObjectType>();
for (ObjectType interfaceType : functionType.getExtendedInterfaces()) {
currentProperties.clear();
checkInterfaceConflictProperties(t, n, functionPrivateName,
properties, currentProperties, interfaceType);
properties.putAll(currentProperties);
}
}
}
}
/**
* Visits a CALL node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
*/
private void visitCall(NodeTraversal t, Node n) {
Node child = n.getFirstChild();
JSType childType = getJSType(child).restrictByNotNullOrUndefined();
if
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> (!childType.canBeCalled()) {
report(t, n, NOT_CALLABLE, childType.toString());
ensureTyped(t, n);
return;
}
// A couple of types can be called as if they were functions.
// If it is a function type, then validate parameters.
if (childType.isFunctionType()) {
FunctionType functionType = childType.toMaybeFunctionType();
boolean isExtern = false;
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if (functionJSDocInfo != null &&
functionJSDocInfo.getAssociatedNode() != null) {
isExtern = functionJSDocInfo.getAssociatedNode().isFromExterns();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType())) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explicit 'this' types must be called in a GETPROP
// or GETELEM.
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!(functionType.getTypeOfThis().toObjectType() != null &&
functionType.getTypeOfThis().toObjectType().isNativeObjectType()) &&
!(child.isGetElem() ||
child.isGetProp())) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO(nicksantos): Add something to check for calls of RegExp objects,
// which is not supported by IE. Either say something about the return type
// or warn about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
private void visitParameterList(NodeTraversal t, Node call,
FunctionType functionType) {
Iterator<Node> arguments = call.children().iterator();
arguments.next(); // skip the function name
Iterator<Node> parameters = functionType.getParameters().iterator();
int ordinal = 0;
Node parameter = null;
Node argument = null;
while (arguments.hasNext() &&
(parameters.hasNext() ||
parameter != null && parameter.isVarArgs())) {
// If there are no parameters left in the list, then the while loop
// above implies that this must be a var_args function.
if (parameters.hasNext()) {
parameter = parameters.next();
}
argument = arguments.next();
ordinal++;
validator.expectArgumentMatchesParameter(t, argument,
getJSType(argument), getJSType(parameter), call, ordinal);
}
int numArgs = call.getChildCount() - 1;
int minArgs = functionType.getMinArguments();
int maxArgs = functionType.getMaxArguments();
if (min
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>Args > numArgs || maxArgs < numArgs) {
report(t, call, WRONG_ARGUMENT_COUNT,
validator.getReadableJSTypeName(call.getFirstChild(), false),
String.valueOf(numArgs), String.valueOf(minArgs),
maxArgs != Integer.MAX_VALUE ?
" and no more than " + maxArgs + " argument(s)" : "");
}
}
/**
* Visits a RETURN node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
*/
private void visitReturn(NodeTraversal t, Node n) {
JSType jsType = getJSType(t.getEnclosingFunction());
if (jsType.isFunctionType()) {
FunctionType functionType = jsType.toMaybeFunctionType();
JSType returnType = functionType.getReturnType();
// if no return type is specified, undefined must be returned
// (it's a void function)
if (returnType == null) {
returnType = getNativeType(VOID_TYPE);
}
// fetching the returned value's type
Node valueNode = n.getFirstChild();
JSType actualReturnType;
if (valueNode == null) {
actualReturnType = getNativeType(VOID_TYPE);
valueNode = n;
} else {
actualReturnType = getJSType(valueNode);
}
// verifying
validator.expectCanAssignTo(t, valueNode, actualReturnType, returnType,
"inconsistent return type");
}
}
/**
* This function unifies the type checking involved in the core binary
* operators and the corresponding assignment operators. The representation
* used internally is such that common code can handle both kinds of
* operators easily.
*
* @param op The operator.
* @param t The traversal object, needed to report errors.
* @param n The node being checked.
*/
private void visitBinaryOperator(int op, NodeTraversal t, Node n) {
Node left = n.getFirstChild();
JSType leftType = getJSType(left);
Node right = n.getLastChild();
JSType rightType = getJSType(right);
switch (op) {
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.LSH:
case Token.RSH:
case Token.ASSIGN_URSH:
case Token.URSH:
if (!leftType.matchesInt32Context()) {
report(t, left, BIT_OPERATION,
NodeUtil.opToStr(n.getType()), leftType.toString());
}
if (!rightType.matchesUint32Context()) {
report(t, right, BIT_OPERATION,
NodeUtil.opToStr(n.getType()), rightType.toString());
}
break;
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
case Token.ASSIGN_MUL:
case Token.ASSIGN_SUB:
case Token.DIV:
case Token.MOD:
case Token.MUL:
case Token.SUB:
validator.expectNumber(t, left, leftType, "left operand");
validator.expectNumber(t, right,
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>/*
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Rhino code, released
* May 6, 1999.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1997-1999
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Bob Jervis
* Google Inc.
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU General Public License Version 2 or later (the "GPL"), in which
* case the provisions of the GPL are applicable instead of those above. If
* you wish to allow use of your version of this file only under the terms of
* the GPL and not to allow others to use your version of this file under the
* MPL, indicate your decision by deleting the provisions above and replacing
* them with the notice and other provisions required by the GPL. If you do
* not delete the provisions above, a recipient may use your version of this
* file under either the MPL or the GPL.
*
* ***** END LICENSE BLOCK ***** */
package com.google.javascript.rhino.jstype;
/**
* An unresolved type that was forward declared. So we know it exists,
* but that it wasn't pulled into this binary.
*
* In most cases, it behaves like a bottom type in the type lattice:
* no real type should be assigned to a NoResolvedType, but the
* NoResolvedType is a subtype of everything. In a few cases, it behaves
* like the unknown type: properties of this type are also NoResolved types,
* and comparisons to other types always have an unknown result.
*
* @author nicksantos@google.com (Nick Santos)
*/
class NoResolvedType extends NoType {
private static final long serialVersionUID = 1L;
NoResolvedType(JSTypeRegistry registry) {
super(registry);
}
@Override
public boolean isNoResolvedType() {
return true;
}
@Override
public boolean isNoType() {
return false;
}
@Override
public boolean isConstructor() {
return false;
}
@Override
public boolean isSubtype(JSType that) {
if (JSType.isSubtypeHelper(this, that)) {
return true;
} else {
return !that.isNoType();
}
}
@Override
String toStringHelper(boolean forAnnotations) {
return forAnnotations ? "?" : "NoResolvedType";
}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>/*
* Copyright 2010 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.collect.ImmutableSet;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.jscomp.regex.RegExpTree;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.Set;
/**
* Look for references to the global RegExp object that would cause
* regular expressions to be unoptimizable, and checks that regular expressions
* are syntactically valid.
*
* @author johnlenz@google.com (John Lenz)
*/
class CheckRegExp extends AbstractPostOrderCallback implements CompilerPass {
static final DiagnosticType REGEXP_REFERENCE =
DiagnosticType.warning("JSC_REGEXP_REFERENCE",
"References to the global RegExp object prevents " +
"optimization of regular expressions.");
static final DiagnosticType MALFORMED_REGEXP = DiagnosticType.warning(
"JSC_MALFORMED_REGEXP",
"Malformed Regular Expression: {0}");
private static final Set<String> REGEXP_PROPERTY_BLACKLIST = ImmutableSet.of(
"$1", "$2", "$3", "$4", "$5", "$6", "$7", "$8", "$9",
"$_", "$input",
// The following would also be blacklisted, but they aren't valid
// identifiers, so can't be accessed with the '.' operator anyway.
// "$*", "$&", "$+", "$`", "$'",
"input", "lastMatch", "lastParen", "leftContext", "rightContext",
"global", "ignoreCase", "lastIndex", "multiline", "source");
private final AbstractCompiler compiler;
private boolean globalRegExpPropertiesUsed = false;
public boolean isGlobalRegExpPropertiesUsed() {
return globalRegExpPropertiesUsed;
}
public CheckRegExp(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, root, this);
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isReferenceName(n)) {
String name = n.getString();
if (name.equals("RegExp") && t.getScope().getVar(name) == null) {
int parentType = parent.getType();
boolean first = (n == parent.getFirstChild());
if (!((parentType == Token.NEW && first)
|| (parentType == Token.CALL &&
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> private JSDocInfo handleJsDoc(AstNode node, Node irNode) {
Comment comment = node.getJsDocNode();
if (comment != null) {
JsDocInfoParser jsDocParser = createJsDocInfoParser(comment, irNode);
parsedComments.add(comment);
if (!handlePossibleFileOverviewJsDoc(jsDocParser)) {
JSDocInfo info = jsDocParser.retrieveAndResetParsedJSDocInfo();
if (info != null) {
validateTypeAnnotations(info, node);
}
return info;
}
}
return null;
}
private void validateTypeAnnotations(JSDocInfo info, AstNode node) {
if (info.hasType()) {
boolean valid = false;
switch (node.getType()) {
// Casts are valid
case com.google.javascript.rhino.head.Token.LP:
valid = node instanceof ParenthesizedExpression;
break;
// Variable declarations are valid
case com.google.javascript.rhino.head.Token.VAR:
valid = true;
break;
// Function declarations are valid
case com.google.javascript.rhino.head.Token.FUNCTION:
FunctionNode fnNode = (FunctionNode) node;
valid = fnNode.getFunctionType() == FunctionNode.FUNCTION_STATEMENT;
break;
// Object literal properties, catch declarations and variable
// initializers are valid.
case com.google.javascript.rhino.head.Token.NAME:
AstNode parent = node.getParent();
valid = parent instanceof ObjectProperty
|| parent instanceof CatchClause
|| parent instanceof FunctionNode
|| (parent instanceof VariableInitializer &&
node == ((VariableInitializer) parent).getTarget());
break;
// Object literal properties are valid
case com.google.javascript.rhino.head.Token.GET:
case com.google.javascript.rhino.head.Token.SET:
case com.google.javascript.rhino.head.Token.NUMBER:
case com.google.javascript.rhino.head.Token.STRING:
valid = node.getParent() instanceof ObjectProperty;
break;
// Property assignments are valid, if at the root of an expression.
case com.google.javascript.rhino.head.Token.ASSIGN:
if (node instanceof Assignment) {
valid = isExprStmt(node.getParent())
&& isPropAccess(((Assignment) node).getLeft());
}
break;
// Property definitions are valid, if at the root of an expression.
case com.google.javascript.rhino.head.Token.GETPROP:
case com.google.javascript.rhino.head.Token.GETELEM:
valid = isExprStmt(node.getParent());
break;
case com.google.javascript.rhino.head.Token.CALL:
valid = info.isDefine();
break;
}
if (!valid) {
errorReporter.warning(MISPLACED_TYPE_ANNOTATION,
sourceName,
node.getLineno(), "", 0);
}
}
}
private boolean isPropAccess(AstNode node) {
return node.getType() == com.google.javascript.rhino.head.Token.GETPROP
|| node.getType() == com.google.javascript
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>.rhino.head.Token.GETELEM;
}
private boolean isExprStmt(AstNode node) {
return node.getType() == com.google.javascript.rhino.head.Token.EXPR_RESULT
|| node.getType() == com.google.javascript.rhino.head.Token.EXPR_VOID;
}
private Node transform(AstNode node) {
Node irNode = justTransform(node);
JSDocInfo jsDocInfo = handleJsDoc(node, irNode);
if (jsDocInfo != null) {
irNode = maybeInjectCastNode(node, jsDocInfo, irNode);
irNode.setJSDocInfo(jsDocInfo);
}
setSourceInfo(irNode, node);
return irNode;
}
private Node maybeInjectCastNode(AstNode node, JSDocInfo info, Node irNode) {
if (node.getType() == com.google.javascript.rhino.head.Token.LP
&& node instanceof ParenthesizedExpression
&& info.hasType()) {
irNode = newNode(Token.CAST, irNode);
}
return irNode;
}
/**
* Parameter NAMEs are special, because they can have inline type docs
* attached.
*
* function f(/** string */ x) {}
* annotates 'x' as a string.
*
* @see <a href="http://code.google.com/p/jsdoc-toolkit/wiki/InlineDocs">
* Using Inline Doc Comments</a>
*/
private Node transformParameter(AstNode node) {
Node irNode = justTransform(node);
Comment comment = node.getJsDocNode();
if (comment != null) {
JSDocInfo info = parseInlineTypeDoc(comment, irNode);
if (info != null) {
irNode.setJSDocInfo(info);
}
}
setSourceInfo(irNode, node);
return irNode;
}
private Node transformNameAsString(Name node) {
Node irNode = transformDispatcher.processName(node, true);
JSDocInfo jsDocInfo = handleJsDoc(node, irNode);
if (jsDocInfo != null) {
irNode.setJSDocInfo(jsDocInfo);
}
setSourceInfo(irNode, node);
return irNode;
}
private Node transformNumberAsString(NumberLiteral literalNode) {
Node irNode = newStringNode(getStringValue(literalNode.getNumber()));
JSDocInfo jsDocInfo = handleJsDoc(literalNode, irNode);
if (jsDocInfo != null) {
irNode.setJSDocInfo(jsDocInfo);
}
setSourceInfo(irNode, literalNode);
return irNode;
}
private static String getStringValue(double value) {
long longValue = (long) value;
// Return "1" instead of "1.0"
if (longValue == value) {
return Long.toString(longValue);
} else {
return Double.toString(value);
}
}
private void setSourceInfo(Node irNode, AstNode node) {
if (irNode.getLineno() == -1) {
// If
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> we didn't already set the line, then set it now. This avoids
// cases like ParenthesizedExpression where we just return a previous
// node, but don't want the new node to get its parent's line number.
int lineno = node.getLineno();
irNode.setLineno(lineno);
int charno = position2charno(node.getAbsolutePosition());
irNode.setCharno(charno);
maybeSetLengthFrom(irNode, node);
}
}
/**
* Creates a JsDocInfoParser and parses the JsDoc string.
*
* Used both for handling individual JSDoc comments and for handling
* file-level JSDoc comments (@fileoverview and @license).
*
* @param node The JsDoc Comment node to parse.
* @param irNode
* @return A JsDocInfoParser. Will contain either fileoverview JsDoc, or
* normal JsDoc, or no JsDoc (if the method parses to the wrong level).
*/
private JsDocInfoParser createJsDocInfoParser(Comment node, Node irNode) {
String comment = node.getValue();
int lineno = node.getLineno();
int position = node.getAbsolutePosition();
// The JsDocInfoParser expects the comment without the initial '/**'.
int numOpeningChars = 3;
JsDocInfoParser jsdocParser =
new JsDocInfoParser(
new JsDocTokenStream(comment.substring(numOpeningChars),
lineno,
position2charno(position) + numOpeningChars),
node,
irNode,
config,
errorReporter);
jsdocParser.setFileLevelJsDocBuilder(fileLevelJsDocBuilder);
jsdocParser.setFileOverviewJSDocInfo(fileOverviewInfo);
jsdocParser.parse();
return jsdocParser;
}
/**
* Parses inline type info.
*/
private JSDocInfo parseInlineTypeDoc(Comment node, Node irNode) {
String comment = node.getValue();
int lineno = node.getLineno();
int position = node.getAbsolutePosition();
// The JsDocInfoParser expects the comment without the initial '/**'.
int numOpeningChars = 3;
JsDocInfoParser parser =
new JsDocInfoParser(
new JsDocTokenStream(comment.substring(numOpeningChars),
lineno,
position2charno(position) + numOpeningChars),
node,
irNode,
config,
errorReporter);
return parser.parseInlineTypeDoc();
}
// Set the length on the node if we're in IDE mode.
private void maybeSetLengthFrom(Node node, AstNode source) {
if (config.isIdeMode) {
node.setLength(source.getLength());
}
}
private int position2charno(int position) {
int lineIndex = sourceString.lastIndexOf('\n', position);
if (lineIndex == -1) {
return position;
} else {
// Subtract one for initial position being 0.
return position - lineIndex - 1;
}
}
private Node justTransform(AstNode node) {
return transformDispatcher.process(node);
}
private class TransformDispatcher extends TypeSafeDispatcher<Node> {
private Node processGeneric(
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>
com.google.javascript.rhino.head.Node n) {
Node node = newNode(transformTokenType(n.getType()));
for (com.google.javascript.rhino.head.Node child : n) {
node.addChildToBack(transform((AstNode) child));
}
return node;
}
/**
* Transforms the given node and then sets its type to Token.STRING if it
* was Token.NAME. If its type was already Token.STRING, then quotes it.
* Used for properties, as the old AST uses String tokens, while the new one
* uses Name tokens for unquoted strings. For example, in
* var o = {'a' : 1, b: 2};
* the string 'a' is quoted, while the name b is turned into a string, but
* unquoted.
*/
private Node transformAsString(AstNode n) {
Node ret;
if (n instanceof Name) {
ret = transformNameAsString((Name) n);
} else if (n instanceof NumberLiteral) {
ret = transformNumberAsString((NumberLiteral) n);
ret.putBooleanProp(Node.QUOTED_PROP, true);
} else {
ret = transform(n);
ret.putBooleanProp(Node.QUOTED_PROP, true);
}
Preconditions.checkState(ret.isString());
return ret;
}
@Override
Node processArrayLiteral(ArrayLiteral literalNode) {
if (literalNode.isDestructuring()) {
reportDestructuringAssign(literalNode);
}
Node node = newNode(Token.ARRAYLIT);
for (AstNode child : literalNode.getElements()) {
Node c = transform(child);
node.addChildToBack(c);
}
return node;
}
@Override
Node processAssignment(Assignment assignmentNode) {
Node assign = processInfixExpression(assignmentNode);
Node target = assign.getFirstChild();
if (!validAssignmentTarget(target)) {
errorReporter.error(
"invalid assignment target",
sourceName,
target.getLineno(), "", 0);
}
return assign;
}
@Override
Node processAstRoot(AstRoot rootNode) {
Node node = newNode(Token.SCRIPT);
for (com.google.javascript.rhino.head.Node child : rootNode) {
node.addChildToBack(transform((AstNode) child));
}
parseDirectives(node);
return node;
}
/**
* Parse the directives, encode them in the AST, and remove their nodes.
*
* For information on ES5 directives, see section 14.1 of
* ECMA-262, Edition 5.
*
* It would be nice if Rhino would eventually take care of this for
* us, but right now their directive-processing is a one-off.
*/
private void parseDirectives(Node node) {
// Remove all the directives, and encode them in the AST.
Set<String> directives = null;
while (isDirective(node.getFirstChild())) {
String directive = node.removeFirstChild().getFirstChild().getString();
if (directives == null) {
directives = Sets.newHashSet(directive);
} else {
directives.add
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>(directive);
}
}
if (directives != null) {
node.setDirectives(directives);
}
}
private boolean isDirective(Node n) {
if (n == null) {
return false;
}
int nType = n.getType();
return nType == Token.EXPR_RESULT &&
n.getFirstChild().isString() &&
ALLOWED_DIRECTIVES.contains(n.getFirstChild().getString());
}
@Override
Node processBlock(Block blockNode) {
return processGeneric(blockNode);
}
@Override
Node processBreakStatement(BreakStatement statementNode) {
Node node = newNode(Token.BREAK);
if (statementNode.getBreakLabel() != null) {
Node labelName = transform(statementNode.getBreakLabel());
// Change the NAME to LABEL_NAME
labelName.setType(Token.LABEL_NAME);
node.addChildToBack(labelName);
}
return node;
}
@Override
Node processCatchClause(CatchClause clauseNode) {
AstNode catchVar = clauseNode.getVarName();
Node node = newNode(Token.CATCH, transform(catchVar));
if (clauseNode.getCatchCondition() != null) {
errorReporter.error(
"Catch clauses are not supported",
sourceName,
clauseNode.getCatchCondition().getLineno(), "", 0);
}
node.addChildToBack(transformBlock(clauseNode.getBody()));
return node;
}
@Override
Node processConditionalExpression(ConditionalExpression exprNode) {
return newNode(
Token.HOOK,
transform(exprNode.getTestExpression()),
transform(exprNode.getTrueExpression()),
transform(exprNode.getFalseExpression()));
}
@Override
Node processContinueStatement(ContinueStatement statementNode) {
Node node = newNode(Token.CONTINUE);
if (statementNode.getLabel() != null) {
Node labelName = transform(statementNode.getLabel());
// Change the NAME to LABEL_NAME
labelName.setType(Token.LABEL_NAME);
node.addChildToBack(labelName);
}
return node;
}
@Override
Node processDoLoop(DoLoop loopNode) {
return newNode(
Token.DO,
transformBlock(loopNode.getBody()),
transform(loopNode.getCondition()));
}
@Override
Node processElementGet(ElementGet getNode) {
return newNode(
Token.GETELEM,
transform(getNode.getTarget()),
transform(getNode.getElement()));
}
@Override
Node processEmptyExpression(EmptyExpression exprNode) {
Node node = newNode(Token.EMPTY);
return node;
}
@Override
Node processEmptyStatement(EmptyStatement exprNode) {
Node node = newNode(Token.EMPTY);
return node;
}
@Override
Node processExpressionStatement(ExpressionStatement statementNode) {
Node node = newNode(transformTokenType(statementNode.getType()));
node.addChildToBack(transform(statementNode.getExpression()));
return node;
}
@Override
Node processForInLoop(ForInLoop loopNode) {
if (loopNode.isForEach()) {
errorReporter.error(
"unsupported language extension: for each",
sourceName,
loopNode.getLineno(), "", 0
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>);
// Return the bare minimum to put the AST in a valid state.
return newNode(Token.EXPR_RESULT, Node.newNumber(0));
}
return newNode(
Token.FOR,
transform(loopNode.getIterator()),
transform(loopNode.getIteratedObject()),
transformBlock(loopNode.getBody()));
}
@Override
Node processForLoop(ForLoop loopNode) {
Node node = newNode(
Token.FOR,
transform(loopNode.getInitializer()),
transform(loopNode.getCondition()),
transform(loopNode.getIncrement()));
node.addChildToBack(transformBlock(loopNode.getBody()));
return node;
}
@Override
Node processFunctionCall(FunctionCall callNode) {
Node node = newNode(transformTokenType(callNode.getType()),
transform(callNode.getTarget()));
for (AstNode child : callNode.getArguments()) {
node.addChildToBack(transform(child));
}
node.setLineno(node.getFirstChild().getLineno());
node.setCharno(node.getFirstChild().getCharno());
maybeSetLengthFrom(node, callNode);
return node;
}
@Override
Node processFunctionNode(FunctionNode functionNode) {
Name name = functionNode.getFunctionName();
Boolean isUnnamedFunction = false;
if (name == null) {
int functionType = functionNode.getFunctionType();
if (functionType != FunctionNode.FUNCTION_EXPRESSION) {
errorReporter.error(
"unnamed function statement",
sourceName,
functionNode.getLineno(), "", 0);
// Return the bare minimum to put the AST in a valid state.
return newNode(Token.EXPR_RESULT, Node.newNumber(0));
}
name = new Name();
name.setIdentifier("");
isUnnamedFunction = true;
}
Node node = newNode(Token.FUNCTION);
Node newName = transform(name);
if (isUnnamedFunction) {
// Old Rhino tagged the empty name node with the line number of the
// declaration.
newName.setLineno(functionNode.getLineno());
// TODO(bowdidge) Mark line number of paren correctly.
// Same problem as below - the left paren might not be on the
// same line as the function keyword.
int lpColumn = functionNode.getAbsolutePosition() +
functionNode.getLp();
newName.setCharno(position2charno(lpColumn));
maybeSetLengthFrom(newName, name);
}
node.addChildToBack(newName);
Node lp = newNode(Token.PARAM_LIST);
// The left paren's complicated because it's not represented by an
// AstNode, so there's nothing that has the actual line number that it
// appeared on. We know the paren has to appear on the same line as the
// function name (or else a semicolon will be inserted.) If there's no
// function name, assume the paren was on the same line as the function.
// TODO(bowdidge): Mark line number of paren correctly.
Name fnName = functionNode.getFunctionName();
if (fnName != null) {
lp.setLineno(fnName.getLineno());
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> } else {
lp.setLineno(functionNode.getLineno());
}
int lparenCharno = functionNode.getLp() +
functionNode.getAbsolutePosition();
lp.setCharno(position2charno(lparenCharno));
for (AstNode param : functionNode.getParams()) {
Node paramNode = transformParameter(param);
// When in ideMode Rhino can generate a param list with only a single
// ErrorNode. This is transformed into an EMPTY node. Drop this node in
// ideMode to keep the AST in a valid state.
if (paramNode.isName()) {
lp.addChildToBack(paramNode);
} else {
// We expect this in ideMode or when there is an error handling
// destructuring parameter assignments which aren't supported
// (an error has already been reported).
Preconditions.checkState(
config.isIdeMode
|| paramNode.isObjectLit()
|| paramNode.isArrayLit());
}
}
node.addChildToBack(lp);
Node bodyNode = transform(functionNode.getBody());
if (!bodyNode.isBlock()) {
// When in ideMode Rhino tries to parse some constructs the compiler
// doesn't support, repair it here. see Rhino's
// Parser#parseFunctionBodyExpr.
Preconditions.checkState(config.isIdeMode);
bodyNode = IR.block();
}
parseDirectives(bodyNode);
node.addChildToBack(bodyNode);
return node;
}
@Override
Node processIfStatement(IfStatement statementNode) {
Node node = newNode(Token.IF);
node.addChildToBack(transform(statementNode.getCondition()));
node.addChildToBack(transformBlock(statementNode.getThenPart()));
if (statementNode.getElsePart() != null) {
node.addChildToBack(transformBlock(statementNode.getElsePart()));
}
return node;
}
@Override
Node processInfixExpression(InfixExpression exprNode) {
Node n = newNode(
transformTokenType(exprNode.getType()),
transform(exprNode.getLeft()),
transform(exprNode.getRight()));
n.setLineno(exprNode.getLineno());
n.setCharno(position2charno(exprNode.getAbsolutePosition()));
maybeSetLengthFrom(n, exprNode);
return n;
}
@Override
Node processKeywordLiteral(KeywordLiteral literalNode) {
return newNode(transformTokenType(literalNode.getType()));
}
@Override
Node processLabel(Label labelNode) {
return newStringNode(Token.LABEL_NAME, labelNode.getName());
}
@Override
Node processLabeledStatement(LabeledStatement statementNode) {
Node node = newNode(Token.LABEL);
Node prev = null;
Node cur = node;
for (Label label : statementNode.getLabels()) {
if (prev != null) {
prev.addChildToBack(cur);
}
cur.addChildToBack(transform(label));
cur.setLineno(label.getLineno());
maybeSetLengthFrom(cur, label);
int clauseAbsolutePosition =
position2charno(label.getAbsolutePosition());
cur.setCharno(
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>clauseAbsolutePosition);
prev = cur;
cur = newNode(Token.LABEL);
}
prev.addChildToBack(transform(statementNode.getStatement()));
return node;
}
@Override
Node processName(Name nameNode) {
return processName(nameNode, false);
}
Node processName(Name nameNode, boolean asString) {
if (asString) {
return newStringNode(Token.STRING, nameNode.getIdentifier());
} else {
if (isReservedKeyword(nameNode.getIdentifier())) {
errorReporter.error(
"identifier is a reserved word",
sourceName,
nameNode.getLineno(), "", 0);
}
return newStringNode(Token.NAME, nameNode.getIdentifier());
}
}
private boolean isAllowedProp(String identifier) {
if (config.languageMode == LanguageMode.ECMASCRIPT3) {
return !TokenStream.isKeyword(identifier);
}
return true;
}
private boolean isReservedKeyword(String identifier) {
if (config.languageMode == LanguageMode.ECMASCRIPT3) {
return TokenStream.isKeyword(identifier);
}
return reservedKeywords != null && reservedKeywords.contains(identifier);
}
@Override
Node processNewExpression(NewExpression exprNode) {
Node node = newNode(
transformTokenType(exprNode.getType()),
transform(exprNode.getTarget()));
for (AstNode child : exprNode.getArguments()) {
node.addChildToBack(transform(child));
}
node.setLineno(exprNode.getLineno());
node.setCharno(position2charno(exprNode.getAbsolutePosition()));
maybeSetLengthFrom(node, exprNode);
return node;
}
@Override
Node processNumberLiteral(NumberLiteral literalNode) {
return newNumberNode(literalNode.getNumber());
}
@Override
Node processObjectLiteral(ObjectLiteral literalNode) {
if (literalNode.isDestructuring()) {
reportDestructuringAssign(literalNode);
}
Node node = newNode(Token.OBJECTLIT);
for (ObjectProperty el : literalNode.getElements()) {
if (config.languageMode == LanguageMode.ECMASCRIPT3) {
if (el.isGetter()) {
reportGetter(el);
continue;
} else if (el.isSetter()) {
reportSetter(el);
continue;
}
}
AstNode rawKey = el.getLeft();
Node key = transformAsString(rawKey);
key.setType(Token.STRING_KEY);
if (rawKey instanceof Name && !isAllowedProp(key.getString())) {
errorReporter.warning(INVALID_ES3_PROP_NAME, sourceName,
key.getLineno(), "", key.getCharno());
}
Node value = transform(el.getRight());
if (el.isGetter()) {
key.setType(Token.GETTER_DEF);
Preconditions.checkState(value.isFunction());
if (getFnParamNode(value).hasChildren()) {
reportGetterParam(el.getLeft());
}
} else if (el.isSetter()) {
key.setType(Token.SETTER_DEF);
Preconditions.checkState(value.
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> later, and is not understood
// on IE. So we need to preserve it as-is. This is really
// obnoxious, because we do not have a good way to represent
// how the original string was encoded without making the
// representation of strings much more complicated.
//
// To handle this, we look at the original source test, and
// mark the string as \v-encoded or not. If a string is
// \v encoded, then all the vertical tabs in that string
// will be encoded with a \v.
int start = literalNode.getAbsolutePosition();
int end = start + literalNode.getLength();
if (start < sourceString.length() &&
(sourceString.substring(
start, Math.min(sourceString.length(), end))
.indexOf("\\v") != -1)) {
n.putBooleanProp(Node.SLASH_V, true);
}
}
return n;
}
@Override
Node processSwitchCase(SwitchCase caseNode) {
Node node;
if (caseNode.isDefault()) {
node = newNode(Token.DEFAULT_CASE);
} else {
AstNode expr = caseNode.getExpression();
node = newNode(Token.CASE, transform(expr));
}
Node block = newNode(Token.BLOCK);
block.putBooleanProp(Node.SYNTHETIC_BLOCK_PROP, true);
block.setLineno(caseNode.getLineno());
block.setCharno(position2charno(caseNode.getAbsolutePosition()));
maybeSetLengthFrom(block, caseNode);
if (caseNode.getStatements() != null) {
for (AstNode child : caseNode.getStatements()) {
block.addChildToBack(transform(child));
}
}
node.addChildToBack(block);
return node;
}
@Override
Node processSwitchStatement(SwitchStatement statementNode) {
Node node = newNode(Token.SWITCH,
transform(statementNode.getExpression()));
for (AstNode child : statementNode.getCases()) {
node.addChildToBack(transform(child));
}
return node;
}
@Override
Node processThrowStatement(ThrowStatement statementNode) {
return newNode(Token.THROW,
transform(statementNode.getExpression()));
}
@Override
Node processTryStatement(TryStatement statementNode) {
Node node = newNode(Token.TRY,
transformBlock(statementNode.getTryBlock()));
Node block = newNode(Token.BLOCK);
boolean lineSet = false;
for (CatchClause cc : statementNode.getCatchClauses()) {
// Mark the enclosing block at the same line as the first catch
// clause.
if (lineSet == false) {
block.setLineno(cc.getLineno());
maybeSetLengthFrom(block, cc);
lineSet = true;
}
block.addChildToBack(transform(cc));
}
node.addChildToBack(block);
AstNode finallyBlock = statementNode.getFinallyBlock();
if (finallyBlock != null) {
node.addChildToBack(transformBlock(finallyBlock));
}
// If we didn't set the line on the catch clause, then
// we've got an empty catch clause
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>. Set its line to be the same
// as the finally block (to match Old Rhino's behavior.)
if ((lineSet == false) && (finallyBlock != null)) {
block.setLineno(finallyBlock.getLineno());
maybeSetLengthFrom(block, finallyBlock);
}
return node;
}
@Override
Node processUnaryExpression(UnaryExpression exprNode) {
int type = transformTokenType(exprNode.getType());
Node operand = transform(exprNode.getOperand());
if (type == Token.NEG && operand.isNumber()) {
operand.setDouble(-operand.getDouble());
return operand;
} else {
if (type == Token.DELPROP &&
!(operand.isGetProp() ||
operand.isGetElem() ||
operand.isName())) {
String msg =
"Invalid delete operand. Only properties can be deleted.";
errorReporter.error(
msg,
sourceName,
operand.getLineno(), "", 0);
} else if (type == Token.INC || type == Token.DEC) {
if (!validAssignmentTarget(operand)) {
String msg = (type == Token.INC)
? "invalid increment target"
: "invalid decrement target";
errorReporter.error(
msg,
sourceName,
operand.getLineno(), "", 0);
}
}
Node node = newNode(type, operand);
if (exprNode.isPostfix()) {
node.putBooleanProp(Node.INCRDECR_PROP, true);
}
return node;
}
}
private boolean validAssignmentTarget(Node target) {
switch (target.getType()) {
case Token.CAST: // CAST is a bit weird, but syntactically valid.
case Token.NAME:
case Token.GETPROP:
case Token.GETELEM:
return true;
}
return false;
}
@Override
Node processVariableDeclaration(VariableDeclaration declarationNode) {
if (!config.acceptConstKeyword && declarationNode.getType() ==
com.google.javascript.rhino.head.Token.CONST) {
processIllegalToken(declarationNode);
}
Node node = newNode(Token.VAR);
for (VariableInitializer child : declarationNode.getVariables()) {
node.addChildToBack(transform(child));
}
return node;
}
@Override
Node processVariableInitializer(VariableInitializer initializerNode) {
Node node = transform(initializerNode.getTarget());
if (initializerNode.getInitializer() != null) {
Node initalizer = transform(initializerNode.getInitializer());
node.addChildToBack(initalizer);
}
return node;
}
@Override
Node processWhileLoop(WhileLoop loopNode) {
return newNode(
Token.WHILE,
transform(loopNode.getCondition()),
transformBlock(loopNode.getBody()));
}
@Override
Node processWithStatement(WithStatement statementNode) {
return newNode(
Token.WITH,
transform(statementNode.getExpression()),
transformBlock(statementNode.getStatement()));
}
@Override
Node processIllegalToken(AstNode node) {
errorReporter.error(
"Unsupported syntax: " +
com.google.javascript.rhino.head.Token.type
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>ToName(
node.getType()),
sourceName,
node.getLineno(), "", 0);
return newNode(Token.EMPTY);
}
void reportDestructuringAssign(AstNode node) {
errorReporter.error(
"destructuring assignment forbidden",
sourceName,
node.getLineno(), "", 0);
}
void reportGetter(AstNode node) {
errorReporter.error(
GETTER_ERROR_MESSAGE,
sourceName,
node.getLineno(), "", 0);
}
void reportSetter(AstNode node) {
errorReporter.error(
SETTER_ERROR_MESSAGE,
sourceName,
node.getLineno(), "", 0);
}
void reportGetterParam(AstNode node) {
errorReporter.error(
"getters may not have parameters",
sourceName,
node.getLineno(), "", 0);
}
void reportSetterParam(AstNode node) {
errorReporter.error(
"setters must have exactly one parameter",
sourceName,
node.getLineno(), "", 0);
}
}
private static int transformTokenType(int token) {
switch (token) {
case com.google.javascript.rhino.head.Token.RETURN:
return Token.RETURN;
case com.google.javascript.rhino.head.Token.BITOR:
return Token.BITOR;
case com.google.javascript.rhino.head.Token.BITXOR:
return Token.BITXOR;
case com.google.javascript.rhino.head.Token.BITAND:
return Token.BITAND;
case com.google.javascript.rhino.head.Token.EQ:
return Token.EQ;
case com.google.javascript.rhino.head.Token.NE:
return Token.NE;
case com.google.javascript.rhino.head.Token.LT:
return Token.LT;
case com.google.javascript.rhino.head.Token.LE:
return Token.LE;
case com.google.javascript.rhino.head.Token.GT:
return Token.GT;
case com.google.javascript.rhino.head.Token.GE:
return Token.GE;
case com.google.javascript.rhino.head.Token.LSH:
return Token.LSH;
case com.google.javascript.rhino.head.Token.RSH:
return Token.RSH;
case com.google.javascript.rhino.head.Token.URSH:
return Token.URSH;
case com.google.javascript.rhino.head.Token.ADD:
return Token.ADD;
case com.google.javascript.rhino.head.Token.SUB:
return Token.SUB;
case com.google.javascript.rhino.head.Token.MUL:
return Token.MUL;
case com.google.javascript.rhino.head.Token.DIV:
return Token.DIV;
case com.google.javascript.rhino.head.Token.MOD:
return Token.MOD;
case com.google.javascript.rhino.head.Token.NOT:
return Token.NOT;
case com.google.javascript.rhino
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>/*
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Rhino code, released
* May 6, 1999.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1997-1999
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Bob Jervis
* Google Inc.
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU General Public License Version 2 or later (the "GPL"), in which
* case the provisions of the GPL are applicable instead of those above. If
* you wish to allow use of your version of this file only under the terms of
* the GPL and not to allow others to use your version of this file under the
* MPL, indicate your decision by deleting the provisions above and replacing
* them with the notice and other provisions required by the GPL. If you do
* not delete the provisions above, a recipient may use your version of this
* file under either the MPL or the GPL.
*
* ***** END LICENSE BLOCK ***** */
package com.google.javascript.rhino.jstype;
import com.google.javascript.rhino.ErrorReporter;
/**
* Value types (null, void, number, boolean, string).
*/
abstract class ValueType extends JSType {
ValueType(JSTypeRegistry registry) {
super(registry);
}
@Override
final JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) {
return this;
}
@Override
public boolean hasDisplayName() {
return true;
}
@Override <T> T visit(RelationshipVisitor<T> visitor, JSType that) {
return visitor.caseValueType(this, that);
}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> sourceExpert.length() means something is missing
// at the end of the line
if (excerpt.equals(LINE)
&& 0 <= charno && charno <= sourceExcerpt.length()) {
for (int i = 0; i < charno; i++) {
char c = sourceExcerpt.charAt(i);
if (Character.isWhitespace(c)) {
b.append(c);
} else {
b.append(' ');
}
}
b.append("^\n");
}
}
return b.toString();
}
/**
* Formats a region by appending line numbers in front, e.g.
* <pre> 9| if (foo) {
* 10| alert('bar');
* 11| }</pre>
* and return line excerpt without any modification.
*/
static class LineNumberingFormatter implements ExcerptFormatter {
@Override
public String formatLine(String line, int lineNumber) {
return line;
}
@Override
public String formatRegion(Region region) {
if (region == null) {
return null;
}
String code = region.getSourceExcerpt();
if (code.length() == 0) {
return null;
}
// max length of the number display
int numberLength = Integer.toString(region.getEndingLineNumber())
.length();
// formatting
StringBuilder builder = new StringBuilder(code.length() * 2);
int start = 0;
int end = code.indexOf('\n', start);
int lineNumber = region.getBeginningLineNumber();
while (start >= 0) {
// line extraction
String line;
if (end < 0) {
line = code.substring(start);
if (line.length() == 0) {
return builder.substring(0, builder.length() - 1);
}
} else {
line = code.substring(start, end);
}
builder.append(" ");
// nice spaces for the line number
int spaces = numberLength - Integer.toString(lineNumber).length();
builder.append(Strings.repeat(" ", spaces));
builder.append(lineNumber);
builder.append("| ");
// end & update
if (end < 0) {
builder.append(line);
start = -1;
} else {
builder.append(line);
builder.append('\n');
start = end + 1;
end = code.indexOf('\n', start);
lineNumber++;
}
}
return builder.toString();
}
}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> }
/**
* Behavior that checks variables for redeclaration or early references
* just after they go out of scope.
*/
private class ReferenceCheckingBehavior implements Behavior {
@Override
public void afterExitScope(NodeTraversal t, ReferenceMap referenceMap) {
// TODO(bashir) In hot-swap version this means that for global scope we
// only go through all global variables accessed in the modified file not
// all global variables. This should be fixed.
// Check all vars after finishing a scope
for (Iterator<Var> it = t.getScope().getVars(); it.hasNext();) {
Var v = it.next();
checkVar(v, referenceMap.getReferences(v).references);
}
}
/**
* If the variable is declared more than once in a basic block, generate a
* warning. Also check if a variable is used in a given scope before it is
* declared, which suggest a likely error. Relies on the fact that
* references is in parse-tree order.
*/
private void checkVar(Var v, List<Reference> references) {
blocksWithDeclarations.clear();
boolean isDeclaredInScope = false;
boolean isUnhoistedNamedFunction = false;
Reference hoistedFn = null;
// Look for hoisted functions.
for (Reference reference : references) {
if (reference.isHoistedFunction()) {
blocksWithDeclarations.add(reference.getBasicBlock());
isDeclaredInScope = true;
hoistedFn = reference;
break;
} else if (NodeUtil.isFunctionDeclaration(
reference.getNode().getParent())) {
isUnhoistedNamedFunction = true;
}
}
for (Reference reference : references) {
if (reference == hoistedFn) {
continue;
}
BasicBlock basicBlock = reference.getBasicBlock();
boolean isDeclaration = reference.isDeclaration();
boolean allowDupe =
SyntacticScopeCreator.hasDuplicateDeclarationSuppression(
reference.getNode(), v);
if (isDeclaration && !allowDupe) {
// Look through all the declarations we've found so far, and
// check if any of them are before this block.
for (BasicBlock declaredBlock : blocksWithDeclarations) {
if (declaredBlock.provablyExecutesBefore(basicBlock)) {
// TODO(johnlenz): Fix AST generating clients that so they would
// have property StaticSourceFile attached at each node. Or
// better yet, make sure the generated code never violates
// the requirement to pass aggressive var check!
String filename = NodeUtil.getSourceName(reference.getNode());
compiler.report(
JSError.make(filename,
reference.getNode(),
checkLevel,
REDECLARED_VARIABLE, v.name));
break;
}
}
}
if (isUnhoistedNamedFunction && !isDeclaration && isDeclaredInScope) {
// Only allow an unhoisted named function to be used within the
// block it is declared.
for (BasicBlock declaredBlock : blocksWithDeclarations) {
if (!declaredBlock.provablyExecutesBefore(basicBlock)) {
String filename = NodeUtil.getSourceName(reference.getNode());
compiler.report(
JSError.make(filename,
reference.getNode(),
AMBIGUOUS_FUNCTION_
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>DECL, v.name));
break;
}
}
}
if (!isDeclaration && !isDeclaredInScope) {
// Don't check the order of refer in externs files.
if (!reference.getNode().isFromExterns()) {
// Special case to deal with var goog = goog || {}
Node grandparent = reference.getGrandparent();
if (grandparent.isName()
&& grandparent.getString() == v.name) {
continue;
}
// Only generate warnings if the scopes do not match in order
// to deal with possible forward declarations and recursion
if (reference.getScope() == v.scope) {
String filename = NodeUtil.getSourceName(reference.getNode());
compiler.report(
JSError.make(filename,
reference.getNode(),
checkLevel,
UNDECLARED_REFERENCE, v.name));
}
}
}
if (isDeclaration) {
blocksWithDeclarations.add(basicBlock);
isDeclaredInScope = true;
}
}
}
}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>/*
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Rhino code, released
* May 6, 1999.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1997-1999
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Bob Jervis
* Google Inc.
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU General Public License Version 2 or later (the "GPL"), in which
* case the provisions of the GPL are applicable instead of those above. If
* you wish to allow use of your version of this file only under the terms of
* the GPL and not to allow others to use your version of this file under the
* MPL, indicate your decision by deleting the provisions above and replacing
* them with the notice and other provisions required by the GPL. If you do
* not delete the provisions above, a recipient may use your version of this
* file under either the MPL or the GPL.
*
* ***** END LICENSE BLOCK ***** */
package com.google.javascript.rhino.jstype;
import static com.google.common.base.Preconditions.checkState;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import com.google.javascript.rhino.ErrorReporter;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import java.util.Set;
/**
* The object type represents instances of JavaScript objects such as
* {@code Object}, {@code Date}, {@code Function}.<p>
*
* Objects in JavaScript are unordered collections of properties.
* Each property consists of a name, a value and a set of attributes.<p>
*
* Each instance has an implicit prototype property ({@code [[Prototype]]})
* pointing to an object instance, which itself has an implicit property, thus
* forming a chain.<p>
*
* A class begins life with no name. Later, a name may be provided once it
* can be inferred. Note that the name in this case is strictly for
* debugging purposes. Looking up type name references goes through the
* {@link JSTypeRegistry}.<p>
*/
class PrototypeObjectType extends ObjectType {
private static final long serialVersionUID = 1L;
private final String className;
private final Property
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>Map properties;
private final boolean nativeType;
// NOTE(nicksantos): The implicit prototype can change over time.
// Modeling this is a bear. Always call getImplicitPrototype(), because
// some subclasses override this to do special resolution handling.
private ObjectType implicitPrototypeFallback;
// If this is a function prototype, then this is the owner.
// A PrototypeObjectType can only be the prototype of one function. If we try
// to do this for multiple functions, then we'll have to create a new one.
private FunctionType ownerFunction = null;
// Whether the toString representation of this should be pretty-printed,
// by printing all properties.
private boolean prettyPrint = false;
private static final int MAX_PRETTY_PRINTED_PROPERTIES = 4;
/**
* Creates an object type.
*
* @param className the name of the class. May be {@code null} to
* denote an anonymous class.
*
* @param implicitPrototype the implicit prototype
* (a.k.a. {@code [[Prototype]]}) as defined by ECMA-262. If the
* implicit prototype is {@code null} the implicit prototype will be
* set to the {@link JSTypeNative#OBJECT_TYPE}.
*/
PrototypeObjectType(JSTypeRegistry registry, String className,
ObjectType implicitPrototype) {
this(registry, className, implicitPrototype, false, null);
}
/**
* Creates an object type, allowing specification of the implicit prototype,
* whether the object is native, and any templatized types.
*/
PrototypeObjectType(JSTypeRegistry registry, String className,
ObjectType implicitPrototype, boolean nativeType,
TemplateTypeMap templateTypeMap) {
super(registry, templateTypeMap);
this.properties = new PropertyMap();
this.properties.setParentSource(this);
this.className = className;
this.nativeType = nativeType;
if (nativeType || implicitPrototype != null) {
setImplicitPrototype(implicitPrototype);
} else {
setImplicitPrototype(
registry.getNativeObjectType(JSTypeNative.OBJECT_TYPE));
}
}
@Override
PropertyMap getPropertyMap() {
return properties;
}
@Override
boolean defineProperty(String name, JSType type, boolean inferred,
Node propertyNode) {
if (hasOwnDeclaredProperty(name)) {
return false;
}
Property newProp = new Property(
name, type, inferred, propertyNode);
properties.putProperty(name, newProp);
return true;
}
@Override
public boolean removeProperty(String name) {
return properties.removeProperty(name);
}
@Override
public void setPropertyJSDocInfo(String propertyName, JSDocInfo info) {
if (info != null) {
if (properties.getOwnProperty(propertyName) == null) {
// If docInfo was attached, but the type of the property
// was not defined anywhere, then we consider this an explicit
// declaration of the property.
defineInferredProperty(propertyName, getPropertyType(propertyName),
null);
}
// The prototype property is not represented as a normal Property.
// We probably don't want to attach any JSDoc to it anyway.
Property property = properties.getOwnProperty(propertyName);
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>;
}
@Override
public FunctionType getConstructor() {
return null;
}
@Override
public ObjectType getImplicitPrototype() {
return implicitPrototypeFallback;
}
/**
* This should only be reset on the FunctionPrototypeType, only to fix an
* incorrectly established prototype chain due to the user having a mismatch
* in super class declaration, and only before properties on that type are
* processed.
*/
final void setImplicitPrototype(ObjectType implicitPrototype) {
checkState(!hasCachedValues());
this.implicitPrototypeFallback = implicitPrototype;
}
@Override
public String getReferenceName() {
if (className != null) {
return className;
} else if (ownerFunction != null) {
return ownerFunction.getReferenceName() + ".prototype";
} else {
return null;
}
}
@Override
public boolean hasReferenceName() {
return className != null || ownerFunction != null;
}
@Override
public boolean isSubtype(JSType that) {
if (JSType.isSubtypeHelper(this, that)) {
return true;
}
// Union types
if (that.isUnionType()) {
// The static {@code JSType.isSubtype} check already decomposed
// union types, so we don't need to check those again.
return false;
}
// record types
if (that.isRecordType()) {
return RecordType.isSubtype(this, that.toMaybeRecordType());
}
// Interfaces
// Find all the interfaces implemented by this class and compare each one
// to the interface instance.
ObjectType thatObj = that.toObjectType();
FunctionType thatCtor = thatObj == null ? null : thatObj.getConstructor();
if (getConstructor() != null && getConstructor().isInterface()) {
for (ObjectType thisInterface : getCtorExtendedInterfaces()) {
if (thisInterface.isSubtype(that)) {
return true;
}
}
} else if (thatCtor != null && thatCtor.isInterface()) {
Iterable<ObjectType> thisInterfaces = getCtorImplementedInterfaces();
for (ObjectType thisInterface : thisInterfaces) {
if (thisInterface.isSubtype(that)) {
return true;
}
}
}
// other prototype based objects
if (isUnknownType() || implicitPrototypeChainIsUnknown()) {
// If unsure, say 'yes', to avoid spurious warnings.
// TODO(user): resolve the prototype chain completely in all cases,
// to avoid guessing.
return true;
}
return thatObj != null && isImplicitPrototype(thatObj);
}
private boolean implicitPrototypeChainIsUnknown() {
ObjectType p = getImplicitPrototype();
while (p != null) {
if (p.isUnknownType()) {
return true;
}
p = p.getImplicitPrototype();
}
return false;
}
@Override
public boolean hasCachedValues() {
return super.hasCachedValues();
}
/** Whether this is a built-in object. */
@Override
public boolean isNativeObjectType() {
return nativeType;
}
@Override
void setOwnerFunction(FunctionType type) {
Preconditions.checkState(ownerFunction == null || type == null);
ownerFunction = type;
}
@Override
public Function
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>Type getOwnerFunction() {
return ownerFunction;
}
@Override
public Iterable<ObjectType> getCtorImplementedInterfaces() {
return isFunctionPrototypeType()
? getOwnerFunction().getImplementedInterfaces()
: ImmutableList.<ObjectType>of();
}
@Override
public Iterable<ObjectType> getCtorExtendedInterfaces() {
return isFunctionPrototypeType()
? getOwnerFunction().getExtendedInterfaces()
: ImmutableList.<ObjectType>of();
}
@Override
JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) {
setResolvedTypeInternal(this);
ObjectType implicitPrototype = getImplicitPrototype();
if (implicitPrototype != null) {
implicitPrototypeFallback =
(ObjectType) implicitPrototype.resolve(t, scope);
}
for (Property prop : properties.values()) {
prop.setType(safeResolve(prop.getType(), t, scope));
}
return this;
}
@Override
public void matchConstraint(JSType constraint) {
// We only want to match constraints on anonymous types.
if (hasReferenceName()) {
return;
}
// Handle the case where the constraint object is a record type.
//
// param constraint {{prop: (number|undefined)}}
// function f(constraint) {}
// f({});
//
// We want to modify the object literal to match the constraint, by
// taking any each property on the record and trying to match
// properties on this object.
if (constraint.isRecordType()) {
matchRecordTypeConstraint(constraint.toObjectType());
} else if (constraint.isUnionType()) {
for (JSType alt : constraint.toMaybeUnionType().getAlternates()) {
if (alt.isRecordType()) {
matchRecordTypeConstraint(alt.toObjectType());
}
}
}
}
public void matchRecordTypeConstraint(ObjectType constraintObj) {
for (String prop : constraintObj.getOwnPropertyNames()) {
JSType propType = constraintObj.getPropertyType(prop);
if (!isPropertyTypeDeclared(prop)) {
JSType typeToInfer = propType;
if (!hasProperty(prop)) {
typeToInfer = getNativeType(JSTypeNative.VOID_TYPE)
.getLeastSupertype(propType);
}
defineInferredProperty(prop, typeToInfer, null);
}
}
}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>LineIndex() {
return lineCount;
}
/** Returns the (zero-based) index of the last column in the text buffer. */
int getColumnIndex() {
return colCount;
}
/** Determines whether the text ends with the given suffix. */
boolean endsWith(String suffix) {
return (sb.length() > suffix.length())
&& suffix.equals(sb.substring(sb.length() - suffix.length()));
}
}
//------------------------------------------------------------------------
// Optimizations
//------------------------------------------------------------------------
public void optimize() {
// Ideally, this pass should be the first pass run, however:
// 1) VariableReferenceCheck reports unexpected warnings if Normalize
// is done first.
// 2) ReplaceMessages, stripCode, and potentially custom passes rely on
// unmodified local names.
normalize();
// Create extern exports after the normalize because externExports depends on unique names.
if (options.isExternExportsEnabled()
|| options.externExportsPath != null) {
externExports();
}
phaseOptimizer = new PhaseOptimizer(this, tracker, null);
if (options.devMode == DevMode.EVERY_PASS) {
phaseOptimizer.setSanityCheck(sanityCheck);
}
if (options.getCheckDeterminism()) {
phaseOptimizer.setPrintAstHashcodes(true);
}
phaseOptimizer.consume(getPassConfig().getOptimizations());
phaseOptimizer.process(externsRoot, jsRoot);
phaseOptimizer = null;
}
@Override
void setCssRenamingMap(CssRenamingMap map) {
options.cssRenamingMap = map;
}
@Override
CssRenamingMap getCssRenamingMap() {
return options.cssRenamingMap;
}
/**
* Reprocesses the current defines over the AST. This is used by GwtCompiler
* to generate N outputs for different targets from the same (checked) AST.
* For each target, we apply the target-specific defines by calling
* {@code processDefines} and then {@code optimize} to optimize the AST
* specifically for that target.
*/
public void processDefines() {
(new DefaultPassConfig(options)).processDefines.create(this)
.process(externsRoot, jsRoot);
}
boolean isInliningForbidden() {
return options.propertyRenaming == PropertyRenamingPolicy.HEURISTIC ||
options.propertyRenaming ==
PropertyRenamingPolicy.AGGRESSIVE_HEURISTIC;
}
/** Control Flow Analysis. */
ControlFlowGraph<Node> computeCFG() {
logger.fine("Computing Control Flow Graph");
Tracer tracer = newTracer("computeCFG");
ControlFlowAnalysis cfa = new ControlFlowAnalysis(this, true, false);
process(cfa);
stopTracer(tracer, "computeCFG");
return cfa.getCfg();
}
public void normalize() {
logger.fine("Normalizing");
startPass("normalize");
process(new Normalize(this, false));
endPass();
}
@Override
void prepareAst(Node root) {
CompilerPass pass = new PrepareAst(this);
pass.process(null, root);
}
void recordFunctionInformation() {
logger.fine("Recording function information");
startPass("
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> output targets without having to perform checking multiple times.
*
* NOTE: This does not include all parts of the compiler's internal state. In
* particular, SourceFiles and CompilerOptions are not recorded. In
* order to recreate a Compiler instance from scratch, you would need to
* call {@code init} with the same arguments as in the initial creation before
* restoring intermediate state.
*/
public static class IntermediateState implements Serializable {
private static final long serialVersionUID = 1L;
Node externsRoot;
private Node jsRoot;
private List<CompilerInput> externs;
private List<CompilerInput> inputs;
private List<JSModule> modules;
private PassConfig.State passConfigState;
private JSTypeRegistry typeRegistry;
private AbstractCompiler.LifeCycleStage lifeCycleStage;
private Map<String, Node> injectedLibraries;
private IntermediateState() {}
}
/**
* Returns the current internal state, excluding the input files and modules.
*/
public IntermediateState getState() {
IntermediateState state = new IntermediateState();
state.externsRoot = externsRoot;
state.jsRoot = jsRoot;
state.externs = externs;
state.inputs = inputs;
state.modules = modules;
state.passConfigState = getPassConfig().getIntermediateState();
state.typeRegistry = typeRegistry;
state.lifeCycleStage = getLifeCycleStage();
state.injectedLibraries = Maps.newLinkedHashMap(injectedLibraries);
return state;
}
/**
* Sets the internal state to the capture given. Note that this assumes that
* the input files are already set up.
*/
public void setState(IntermediateState state) {
externsRoot = state.externsRoot;
jsRoot = state.jsRoot;
externs = state.externs;
inputs = state.inputs;
modules = state.modules;
passes = createPassConfigInternal();
getPassConfig().setIntermediateState(state.passConfigState);
typeRegistry = state.typeRegistry;
setLifeCycleStage(state.lifeCycleStage);
injectedLibraries.clear();
injectedLibraries.putAll(state.injectedLibraries);
}
@VisibleForTesting
List<CompilerInput> getInputsForTesting() {
return inputs;
}
@VisibleForTesting
List<CompilerInput> getExternsForTesting() {
return externs;
}
@Override
boolean hasRegExpGlobalReferences() {
return hasRegExpGlobalReferences;
}
@Override
void setHasRegExpGlobalReferences(boolean references) {
hasRegExpGlobalReferences = references;
}
@Override
void updateGlobalVarReferences(Map<Var, ReferenceCollection> refMapPatch,
Node collectionRoot) {
Preconditions.checkState(collectionRoot.isScript()
|| collectionRoot.isBlock());
if (globalRefMap == null) {
globalRefMap = new GlobalVarReferenceMap(getInputsInOrder(),
getExternsInOrder());
}
globalRefMap.updateGlobalVarReferences(refMapPatch, collectionRoot);
}
@Override
GlobalVarReferenceMap getGlobalVarReferences() {
return globalRefMap;
}
@Override
CompilerInput getSynthesizedExternsInput() {
if (synthesizedExternsInput
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>
* If the programmer assigns other types of values to this property,
* the property will take on the union of all these types.
*
* UNKNOWN properties are properties on the UNKNOWN type. The UNKNOWN
* type has all properties, but we do not know whether they are
* declared or inferred.
*
*/
public abstract class ObjectType extends JSType implements StaticScope<JSType> {
private boolean visited;
private JSDocInfo docInfo = null;
private boolean unknown = true;
ObjectType(JSTypeRegistry registry) {
super(registry);
}
ObjectType(JSTypeRegistry registry, TemplateTypeMap templateTypeMap) {
super(registry, templateTypeMap);
}
@Override
public Node getRootNode() { return null; }
@Override
public ObjectType getParentScope() {
return getImplicitPrototype();
}
/**
* Returns the property map that manages the set of properties for an object.
*/
PropertyMap getPropertyMap() {
return PropertyMap.immutableEmptyMap();
}
/**
* Default getSlot implementation. This gets overridden by FunctionType
* for lazily-resolved prototypes.
*/
@Override
public Property getSlot(String name) {
return getPropertyMap().getSlot(name);
}
@Override
public Property getOwnSlot(String name) {
return getPropertyMap().getOwnProperty(name);
}
@Override
public JSType getTypeOfThis() {
return null;
}
/**
* Gets the declared default element type.
* @see TemplatizedType
*/
public ImmutableList<JSType> getTemplateTypes() {
return null;
}
/**
* Gets the docInfo for this type.
*/
@Override
public JSDocInfo getJSDocInfo() {
return docInfo;
}
/**
* Sets the docInfo for this type from the given
* {@link JSDocInfo}. The {@code JSDocInfo} may be {@code null}.
*/
public void setJSDocInfo(JSDocInfo info) {
docInfo = info;
}
/**
* Detects a cycle in the implicit prototype chain. This method accesses
* the {@link #getImplicitPrototype()} method and must therefore be
* invoked only after the object is sufficiently initialized to respond to
* calls to this method.<p>
*
* @return True iff an implicit prototype cycle was detected.
*/
final boolean detectImplicitPrototypeCycle() {
// detecting cycle
this.visited = true;
ObjectType p = getImplicitPrototype();
while (p != null) {
if (p.visited) {
return true;
} else {
p.visited = true;
}
p = p.getImplicitPrototype();
}
// clean up
p = this;
do {
p.visited = false;
p = p.getImplicitPrototype();
} while (p != null);
return false;
}
/**
* Detects cycles in either the implicit prototype chain, or the implemented/extended
* interfaces.<p>
*
* @return True iff a cycle was detected.
*/
final boolean detectInheritanceCycle() {
// TODO(user): This should get moved to preventing cycles in FunctionTypeBuilder
// rather than removing them here after they have been created.
// Also,
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> this doesn't do the right thing for extended interfaces, though that is
// masked by another bug.
return detectImplicitPrototypeCycle()
|| Iterables.contains(this.getCtorImplementedInterfaces(), this)
|| Iterables.contains(this.getCtorExtendedInterfaces(), this);
}
/**
* Gets the reference name for this object. This includes named types
* like constructors, prototypes, and enums. It notably does not include
* literal types like strings and booleans and structural types.
* @return the object's name or {@code null} if this is an anonymous
* object
*/
public abstract String getReferenceName();
/**
* Due to the complexity of some of our internal type systems, sometimes
* we have different types constructed by the same constructor.
* In other parts of the type system, these are called delegates.
* We construct these types by appending suffixes to the constructor name.
*
* The normalized reference name does not have these suffixes, and as such,
* recollapses these implicit types back to their real type.
*/
public String getNormalizedReferenceName() {
String name = getReferenceName();
if (name != null) {
int pos = name.indexOf("(");
if (pos != -1) {
return name.substring(0, pos);
}
}
return name;
}
@Override
public String getDisplayName() {
return getNormalizedReferenceName();
}
/**
* Creates a suffix for a proxy delegate.
* @see #getNormalizedReferenceName
*/
public static String createDelegateSuffix(String suffix) {
return "(" + suffix + ")";
}
/**
* Returns true if the object is named.
* @return true if the object is named, false if it is anonymous
*/
public boolean hasReferenceName() {
return false;
}
@Override
public TernaryValue testForEquality(JSType that) {
// super
TernaryValue result = super.testForEquality(that);
if (result != null) {
return result;
}
// objects are comparable to everything but null/undefined
if (that.isSubtype(
getNativeType(JSTypeNative.OBJECT_NUMBER_STRING_BOOLEAN))) {
return UNKNOWN;
} else {
return FALSE;
}
}
/**
* Gets this object's constructor.
* @return this object's constructor or {@code null} if it is a native
* object (constructed natively v.s. by instantiation of a function)
*/
public abstract FunctionType getConstructor();
/**
* Gets the implicit prototype (a.k.a. the {@code [[Prototype]]} property).
*/
public abstract ObjectType getImplicitPrototype();
/**
* Defines a property whose type is explicitly declared by the programmer.
* @param propertyName the property's name
* @param type the type
* @param propertyNode the node corresponding to the declaration of property
* which might later be accessed using {@code getPropertyNode}.
*/
public final boolean defineDeclaredProperty(String propertyName,
JSType type, Node propertyNode) {
boolean result = defineProperty(propertyName, type, false, propertyNode);
// All property definitions go through this method
// or defineInferredProperty. Because the properties defined an an
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> // object can affect subtyping, it's slightly more efficient
// to register this after defining the property.
registry.registerPropertyOnType(propertyName, this);
return result;
}
/**
* Defines a property whose type is on a synthesized object. These objects
* don't actually exist in the user's program. They're just used for
* bookkeeping in the type system.
*/
public final boolean defineSynthesizedProperty(String propertyName,
JSType type, Node propertyNode) {
return defineProperty(propertyName, type, false, propertyNode);
}
/**
* Defines a property whose type is inferred.
* @param propertyName the property's name
* @param type the type
* @param propertyNode the node corresponding to the inferred definition of
* property that might later be accessed using {@code getPropertyNode}.
*/
public final boolean defineInferredProperty(String propertyName,
JSType type, Node propertyNode) {
StaticSlot<JSType> originalSlot = getSlot(propertyName);
if (hasProperty(propertyName)) {
if (isPropertyTypeDeclared(propertyName)) {
// We never want to hide a declared property with an inferred property.
return true;
}
JSType originalType = getPropertyType(propertyName);
type = originalType == null ? type :
originalType.getLeastSupertype(type);
}
boolean result = defineProperty(propertyName, type, true,
propertyNode);
// All property definitions go through this method
// or defineDeclaredProperty. Because the properties defined an an
// object can affect subtyping, it's slightly more efficient
// to register this after defining the property.
registry.registerPropertyOnType(propertyName, this);
return result;
}
/**
* Defines a property.<p>
*
* For clarity, callers should prefer {@link #defineDeclaredProperty} and
* {@link #defineInferredProperty}.
*
* @param propertyName the property's name
* @param type the type
* @param inferred {@code true} if this property's type is inferred
* @param propertyNode the node that represents the definition of property.
* Depending on the actual sub-type the node type might be different.
* The general idea is to have an estimate of where in the source code
* this property is defined.
* @return True if the property was registered successfully, false if this
* conflicts with a previous property type declaration.
*/
abstract boolean defineProperty(String propertyName, JSType type,
boolean inferred, Node propertyNode);
/**
* Removes the declared or inferred property from this ObjectType.
*
* @param propertyName the property's name
* @return true if the property was removed successfully. False if the
* property did not exist, or could not be removed.
*/
public boolean removeProperty(String propertyName) {
return false;
}
/**
* Gets the node corresponding to the definition of the specified property.
* This could be the node corresponding to declaration of the property or the
* node corresponding to the first reference to this property, e.g.,
* "this.propertyName" in a constructor. Note this is mainly intended to be
* an estimate of where in the source code a property is defined. Sometime
* the returned node is not even part of the global AST
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> but in the AST of the
* JsDoc that defines a type.
*
* @param propertyName the name of the property
* @return the {@code Node} corresponding to the property or null.
*/
public Node getPropertyNode(String propertyName) {
Property p = getSlot(propertyName);
return p == null ? null : p.getNode();
}
/**
* Gets the docInfo on the specified property on this type. This should not
* be implemented recursively, as you generally need to know exactly on
* which type in the prototype chain the JSDocInfo exists.
*/
public JSDocInfo getOwnPropertyJSDocInfo(String propertyName) {
Property p = getOwnSlot(propertyName);
return p == null ? null : p.getJSDocInfo();
}
/**
* Sets the docInfo for the specified property from the
* {@link JSDocInfo} on its definition.
* @param info {@code JSDocInfo} for the property definition. May be
* {@code null}.
*/
public void setPropertyJSDocInfo(String propertyName, JSDocInfo info) {
// by default, do nothing
}
@Override
public JSType findPropertyType(String propertyName) {
return hasProperty(propertyName) ?
getPropertyType(propertyName) : null;
}
/**
* Gets the property type of the property whose name is given. If the
* underlying object does not have this property, the Unknown type is
* returned to indicate that no information is available on this property.
*
* This gets overridden by FunctionType for lazily-resolved call() and
* bind() functions.
*
* @return the property's type or {@link UnknownType}. This method never
* returns {@code null}.
*/
public JSType getPropertyType(String propertyName) {
StaticSlot<JSType> slot = getSlot(propertyName);
if (slot == null) {
if (isNoResolvedType() || isCheckedUnknownType()) {
return getNativeType(JSTypeNative.CHECKED_UNKNOWN_TYPE);
} else if (isEmptyType()) {
return getNativeType(JSTypeNative.NO_TYPE);
}
return getNativeType(JSTypeNative.UNKNOWN_TYPE);
}
return slot.getType();
}
@Override
public boolean hasProperty(String propertyName) {
// Unknown types have all properties.
return isEmptyType() || isUnknownType() || getSlot(propertyName) != null;
}
/**
* Checks whether the property whose name is given is present directly on
* the object. Returns false even if it is declared on a supertype.
*/
public boolean hasOwnProperty(String propertyName) {
return getOwnSlot(propertyName) != null;
}
/**
* Returns the names of all the properties directly on this type.
*
* Overridden by FunctionType to add "prototype".
*/
public Set<String> getOwnPropertyNames() {
return getPropertyMap().getOwnPropertyNames();
}
/**
* Checks whether the property's type is inferred.
*/
public boolean isPropertyTypeInferred(String propertyName) {
StaticSlot<JSType> slot = getSlot(propertyName);
return slot == null ? false : slot.isTypeInferred();
}
/**
* Checks whether
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> the property's type is declared.
*/
public boolean isPropertyTypeDeclared(String propertyName) {
StaticSlot<JSType> slot = getSlot(propertyName);
return slot == null ? false : !slot.isTypeInferred();
}
/**
* Whether the given property is declared on this object.
*/
final boolean hasOwnDeclaredProperty(String name) {
return hasOwnProperty(name) && isPropertyTypeDeclared(name);
}
/** Checks whether the property was defined in the externs. */
public boolean isPropertyInExterns(String propertyName) {
Property p = getSlot(propertyName);
return p == null ? false : p.isFromExterns();
}
/**
* Gets the number of properties of this object.
*/
public int getPropertiesCount() {
return getPropertyMap().getPropertiesCount();
}
/**
* Returns a list of properties defined or inferred on this type and any of
* its supertypes.
*/
public Set<String> getPropertyNames() {
Set<String> props = Sets.newTreeSet();
collectPropertyNames(props);
return props;
}
/**
* Adds any properties defined on this type or its supertypes to the set.
*/
final void collectPropertyNames(Set<String> props) {
getPropertyMap().collectPropertyNames(props);
}
@Override
public <T> T visit(Visitor<T> visitor) {
return visitor.caseObjectType(this);
}
@Override <T> T visit(RelationshipVisitor<T> visitor, JSType that) {
return visitor.caseObjectType(this, that);
}
/**
* Checks that the prototype is an implicit prototype of this object. Since
* each object has an implicit prototype, an implicit prototype's
* implicit prototype is also this implicit prototype's.
*
* @param prototype any prototype based object
*
* @return {@code true} if {@code prototype} is {@code equal} to any
* object in this object's implicit prototype chain.
*/
final boolean isImplicitPrototype(ObjectType prototype) {
for (ObjectType current = this;
current != null;
current = current.getImplicitPrototype()) {
if (current.isTemplatizedType()) {
current = current.toMaybeTemplatizedType().getReferencedType();
}
if (current.isEquivalentTo(prototype)) {
return true;
}
}
return false;
}
@Override
public BooleanLiteralSet getPossibleToBooleanOutcomes() {
return BooleanLiteralSet.TRUE;
}
/**
* We treat this as the unknown type if any of its implicit prototype
* properties is unknown.
*/
@Override
public boolean isUnknownType() {
// If the object is unknown now, check the supertype again,
// because it might have been resolved since the last check.
if (unknown) {
ObjectType implicitProto = getImplicitPrototype();
if (implicitProto == null ||
implicitProto.isNativeObjectType()) {
unknown = false;
for (ObjectType interfaceType : getCtorExtendedInterfaces()) {
if (interfaceType.isUnknownType()) {
unknown = true;
break;
}
}
} else {
unknown = implicitProto.isUnknownType();
}
}
return unknown;
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> matches(error.getType());
}
/**
* Returns whether the given type matches a type in this group.
*/
public boolean matches(DiagnosticType type) {
return types.contains(type);
}
/**
* Returns whether all of the types in the given group are in this group.
*/
boolean isSubGroup(DiagnosticGroup group) {
for (DiagnosticType type : group.types) {
if (!matches(type)) {
return false;
}
}
return true;
}
/**
* Returns an iterable over all the types in this group.
*/
public Iterable<DiagnosticType> getTypes() {
return types;
}
@Override
public String toString() {
return name == null ? super.toString() : "DiagnosticGroup<" + name + ">";
}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> the enclosing scope.
*/
@Override
JSType resolveInternal(ErrorReporter t, StaticScope<JSType> enclosing) {
// TODO(user): Investigate whether it is really necessary to keep two
// different mechanisms for resolving named types, and if so, which order
// makes more sense. Now, resolution via registry is first in order to
// avoid triggering the warnings built into the resolution via properties.
boolean resolved = resolveViaRegistry(t);
if (detectInheritanceCycle()) {
handleTypeCycle(t);
}
if (resolved) {
super.resolveInternal(t, enclosing);
finishPropertyContinuations();
return registry.isLastGeneration() ?
getReferencedType() : this;
}
resolveViaProperties(t, enclosing);
if (detectInheritanceCycle()) {
handleTypeCycle(t);
}
super.resolveInternal(t, enclosing);
if (isResolved()) {
finishPropertyContinuations();
}
return registry.isLastGeneration() ?
getReferencedType() : this;
}
/**
* Resolves a named type by looking it up in the registry.
* @return True if we resolved successfully.
*/
private boolean resolveViaRegistry(ErrorReporter reporter) {
JSType type = registry.getType(reference);
if (type != null) {
setReferencedAndResolvedType(type, reporter);
return true;
}
return false;
}
/**
* Resolves a named type by looking up its first component in the scope, and
* subsequent components as properties. The scope must have been fully
* parsed and a symbol table constructed.
*/
private void resolveViaProperties(ErrorReporter reporter,
StaticScope<JSType> enclosing) {
JSType value = lookupViaProperties(reporter, enclosing);
// last component of the chain
if (value != null && value.isFunctionType() &&
(value.isConstructor() || value.isInterface())) {
FunctionType functionType = value.toMaybeFunctionType();
setReferencedAndResolvedType(functionType.getInstanceType(), reporter);
} else if (value != null && value.isNoObjectType()) {
setReferencedAndResolvedType(
registry.getNativeFunctionType(
JSTypeNative.NO_OBJECT_TYPE).getInstanceType(), reporter);
} else if (value instanceof EnumType) {
setReferencedAndResolvedType(
((EnumType) value).getElementsType(), reporter);
} else {
// We've been running into issues where people forward-declare
// non-named types. (This is legitimate...our dependency management
// code doubles as our forward-declaration code.)
//
// So if the type does resolve to an actual value, but it's not named,
// then don't respect the forward declaration.
handleUnresolvedType(reporter, value == null || value.isUnknownType());
}
}
/**
* Resolves a type by looking up its first component in the scope, and
* subsequent components as properties. The scope must have been fully
* parsed and a symbol table constructed.
* @return The type of the symbol, or null if the type could not be found.
*/
private JSType lookupViaProperties(ErrorReporter reporter,
StaticScope<JSType> enclosing) {
String[] componentNames = reference
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>.split("\\.", -1);
if (componentNames[0].length() == 0) {
return null;
}
StaticSlot<JSType> slot = enclosing.getSlot(componentNames[0]);
if (slot == null) {
return null;
}
// If the first component has a type of 'Unknown', then any type
// names using it should be regarded as silently 'Unknown' rather than be
// noisy about it.
JSType slotType = slot.getType();
if (slotType == null || slotType.isAllType() || slotType.isNoType()) {
return null;
}
JSType value = getTypedefType(reporter, slot);
if (value == null) {
return null;
}
// resolving component by component
for (int i = 1; i < componentNames.length; i++) {
ObjectType parentClass = ObjectType.cast(value);
if (parentClass == null) {
return null;
}
if (componentNames[i].length() == 0) {
return null;
}
value = parentClass.getPropertyType(componentNames[i]);
}
return value;
}
private void setReferencedAndResolvedType(
JSType type, ErrorReporter reporter) {
if (validator != null) {
validator.apply(type);
}
setReferencedType(type);
checkEnumElementCycle(reporter);
checkProtoCycle(reporter);
setResolvedTypeInternal(getReferencedType());
}
private void handleTypeCycle(ErrorReporter t) {
setReferencedType(
registry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE));
t.warning("Cycle detected in inheritance chain of type " + reference,
sourceName, lineno, charno);
setResolvedTypeInternal(getReferencedType());
}
private void checkEnumElementCycle(ErrorReporter t) {
JSType referencedType = getReferencedType();
if (referencedType instanceof EnumElementType &&
((EnumElementType) referencedType).getPrimitiveType() == this) {
handleTypeCycle(t);
}
}
private void checkProtoCycle(ErrorReporter t) {
JSType referencedType = getReferencedType();
if (referencedType == this) {
handleTypeCycle(t);
}
}
// Warns about this type being unresolved iff it's not a forward-declared
// type name.
private void handleUnresolvedType(
ErrorReporter t, boolean ignoreForwardReferencedTypes) {
if (registry.isLastGeneration()) {
boolean isForwardDeclared =
ignoreForwardReferencedTypes &&
registry.isForwardDeclaredType(reference);
if (!isForwardDeclared && registry.isLastGeneration()) {
t.warning("Bad type annotation. Unknown type " + reference,
sourceName, lineno, charno);
} else {
setReferencedType(
registry.getNativeObjectType(
JSTypeNative.NO_RESOLVED_TYPE));
if (registry.isLastGeneration() && validator != null) {
validator.apply(getReferencedType());
}
}
setResolvedTypeInternal(getReferencedType());
} else {
setResolvedTypeInternal(this);
}
}
private JSType getTypedefType(ErrorReporter t, StaticSlot<JSType> slot) {
JSType type
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> = slot.getType();
if (type != null) {
return type;
}
handleUnresolvedType(t, true);
return null;
}
@Override
public boolean setValidator(Predicate<JSType> validator) {
// If the type is already resolved, we can validate it now. If
// the type has not been resolved yet, we need to wait till its
// resolved before we can validate it.
if (this.isResolved()) {
return super.setValidator(validator);
} else {
this.validator = validator;
return true;
}
}
/** Store enough information to define a property at a later time. */
private static final class PropertyContinuation {
private final String propertyName;
private final JSType type;
private final boolean inferred;
private final Node propertyNode;
private PropertyContinuation(
String propertyName,
JSType type,
boolean inferred,
Node propertyNode) {
this.propertyName = propertyName;
this.type = type;
this.inferred = inferred;
this.propertyNode = propertyNode;
}
void commit(ObjectType target) {
target.defineProperty(
propertyName, type, inferred, propertyNode);
}
}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>/*
* Copyright 2006 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.javascript.rhino.InputId;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
/**
* <p>The syntactic scope creator scans the parse tree to create a Scope object
* containing all the variable declarations in that scope.</p>
*
* <p>This implementation is not thread-safe.</p>
*
*/
class SyntacticScopeCreator implements ScopeCreator {
private final AbstractCompiler compiler;
private Scope scope;
private InputId inputId;
private final RedeclarationHandler redeclarationHandler;
// The arguments variable is special, in that it's declared in every local
// scope, but not explicitly declared.
private static final String ARGUMENTS = "arguments";
public static final DiagnosticType VAR_MULTIPLY_DECLARED_ERROR =
DiagnosticType.error(
"JSC_VAR_MULTIPLY_DECLARED_ERROR",
"Variable {0} first declared in {1}");
public static final DiagnosticType VAR_ARGUMENTS_SHADOWED_ERROR =
DiagnosticType.error(
"JSC_VAR_ARGUMENTS_SHADOWED_ERROR",
"Shadowing \"arguments\" is not allowed");
/**
* Creates a ScopeCreator.
*/
SyntacticScopeCreator(AbstractCompiler compiler) {
this.compiler = compiler;
this.redeclarationHandler = new DefaultRedeclarationHandler();
}
SyntacticScopeCreator(
AbstractCompiler compiler, RedeclarationHandler redeclarationHandler) {
this.compiler = compiler;
this.redeclarationHandler = redeclarationHandler;
}
@Override
public Scope createScope(Node n, Scope parent) {
inputId = null;
if (parent == null) {
scope = Scope.createGlobalScope(n);
} else {
scope = new Scope(parent, n);
}
scanRoot(n);
inputId = null;
Scope returnedScope = scope;
scope = null;
return returnedScope;
}
private void scanRoot(Node n) {
if (n.isFunction()) {
if (inputId == null) {
inputId = NodeUtil.getInputId(n);
// TODO(johnlenz): inputId maybe null if the FUNCTION node is detached
// from the AST.
// Is it meaningful to build a scope for detached FUNCTION node?
}
final Node fnNameNode = n.getFirstChild();
final
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> Node args = fnNameNode.getNext();
final Node body = args.getNext();
// Bleed the function name into the scope, if it hasn't
// been declared in the outer scope.
String fnName = fnNameNode.getString();
if (!fnName.isEmpty() && NodeUtil.isFunctionExpression(n)) {
declareVar(fnNameNode);
}
// Args: Declare function variables
Preconditions.checkState(args.isParamList());
for (Node a = args.getFirstChild(); a != null;
a = a.getNext()) {
Preconditions.checkState(a.isName());
declareVar(a);
}
// Body
scanVars(body);
} else {
// It's the global block
Preconditions.checkState(scope.getParent() == null);
scanVars(n);
}
}
/**
* Scans and gather variables declarations under a Node
*/
private void scanVars(Node n) {
switch (n.getType()) {
case Token.VAR:
// Declare all variables. e.g. var x = 1, y, z;
for (Node child = n.getFirstChild();
child != null;) {
Node next = child.getNext();
declareVar(child);
child = next;
}
return;
case Token.FUNCTION:
if (NodeUtil.isFunctionExpression(n)) {
return;
}
String fnName = n.getFirstChild().getString();
if (fnName.isEmpty()) {
// This is invalid, but allow it so the checks can catch it.
return;
}
declareVar(n.getFirstChild());
return; // should not examine function's children
case Token.CATCH:
Preconditions.checkState(n.getChildCount() == 2);
Preconditions.checkState(n.getFirstChild().isName());
// the first child is the catch var and the third child
// is the code block
final Node var = n.getFirstChild();
final Node block = var.getNext();
declareVar(var);
scanVars(block);
return; // only one child to scan
case Token.SCRIPT:
inputId = n.getInputId();
Preconditions.checkNotNull(inputId);
break;
}
// Variables can only occur in statement-level nodes, so
// we only need to traverse children in a couple special cases.
if (NodeUtil.isControlStructure(n) || NodeUtil.isStatementBlock(n)) {
for (Node child = n.getFirstChild();
child != null;) {
Node next = child.getNext();
scanVars(child);
child = next;
}
}
}
/**
* Interface for injectable duplicate handling.
*/
interface RedeclarationHandler {
void onRedeclaration(
Scope s, String name, Node n, CompilerInput input);
}
/**
* The default handler for duplicate declarations.
*/
private class DefaultRedeclarationHandler implements RedeclarationHandler {
@Override
public void onRedeclaration(
Scope s, String name, Node n, CompilerInput input) {
Node parent = n.getParent();
// Don't allow multiple variables to be declared at the top-level scope
if (scope.isGlobal()) {
Scope.Var origVar = scope.getVar(name);
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>
Node origParent = origVar.getParentNode();
if (origParent.isCatch() &&
parent.isCatch()) {
// Okay, both are 'catch(x)' variables.
return;
}
boolean allowDupe = hasDuplicateDeclarationSuppression(n, origVar);
if (!allowDupe) {
compiler.report(
JSError.make(NodeUtil.getSourceName(n), n,
VAR_MULTIPLY_DECLARED_ERROR,
name,
(origVar.input != null
? origVar.input.getName()
: "??")));
}
} else if (name.equals(ARGUMENTS) && !NodeUtil.isVarDeclaration(n)) {
// Disallow shadowing "arguments" as we can't handle with our current
// scope modeling.
compiler.report(
JSError.make(NodeUtil.getSourceName(n), n,
VAR_ARGUMENTS_SHADOWED_ERROR));
}
}
}
/**
* Declares a variable.
*
* @param n The node corresponding to the variable name.
*/
private void declareVar(Node n) {
Preconditions.checkState(n.isName());
CompilerInput input = compiler.getInput(inputId);
String name = n.getString();
if (scope.isDeclared(name, false)
|| (scope.isLocal() && name.equals(ARGUMENTS))) {
redeclarationHandler.onRedeclaration(
scope, name, n, input);
} else {
scope.declare(name, n, null, input);
}
}
/**
* @param n The name node to check.
* @param origVar The associated Var.
* @return Whether duplicated declarations warnings should be suppressed
* for the given node.
*/
static boolean hasDuplicateDeclarationSuppression(Node n, Scope.Var origVar) {
Preconditions.checkState(n.isName());
Node parent = n.getParent();
Node origParent = origVar.getParentNode();
JSDocInfo info = n.getJSDocInfo();
if (info == null) {
info = parent.getJSDocInfo();
}
if (info != null && info.getSuppressions().contains("duplicate")) {
return true;
}
info = origVar.nameNode.getJSDocInfo();
if (info == null) {
info = origParent.getJSDocInfo();
}
return (info != null && info.getSuppressions().contains("duplicate"));
}
/**
* Generates an untyped global scope from the root of AST of compiler (which
* includes externs).
*
* @param compiler The compiler for which the scope is generated.
* @return The new untyped global scope generated as a result of this call.
*/
static Scope generateUntypedTopScope(AbstractCompiler compiler) {
return new SyntacticScopeCreator(compiler).createScope(compiler.getRoot(),
null);
}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>.out = outState;
}
L getIn() {
return in;
}
void setIn(L in) {
Preconditions.checkNotNull(in);
this.in = in;
}
List<L> getOut() {
return out;
}
void setOut(List<L> out) {
Preconditions.checkNotNull(out);
for (L item : out) {
Preconditions.checkNotNull(item);
}
this.out = out;
}
@Override
public String toString() {
return String.format("IN: %s OUT: %s", in, out);
}
@Override
public int hashCode() {
return Objects.hashCode(in, out);
}
}
/**
* Compute set of escaped variables. When a variable is escaped in a
* dataflow analysis, it can be reference outside of the code that we are
* analyzing. A variable is escaped if any of the following is true:
*
* <p><ol>
* <li>It is defined as the exception name in CATCH clause so it became a
* variable local not to our definition of scope.</li>
* <li>Exported variables as they can be needed after the script terminates.
* </li>
* <li>Names of named functions because in JavaScript, <i>function foo(){}</i>
* does not kill <i>foo</i> in the dataflow.</li>
*/
static void computeEscaped(final Scope jsScope, final Set<Var> escaped,
AbstractCompiler compiler) {
// TODO(user): Very good place to store this information somewhere.
AbstractPostOrderCallback finder = new AbstractPostOrderCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (jsScope == t.getScope() || !n.isName()
|| parent.isFunction()) {
return;
}
String name = n.getString();
Var var = t.getScope().getVar(name);
if (var != null && var.scope == jsScope) {
escaped.add(jsScope.getVar(name));
}
}
};
NodeTraversal t = new NodeTraversal(compiler, finder);
t.traverseAtScope(jsScope);
// 1: Remove the exception name in CATCH which technically isn't local to
// begin with.
for (Iterator<Var> i = jsScope.getVars(); i.hasNext();) {
Var var = i.next();
if (var.getParentNode().isCatch() ||
compiler.getCodingConvention().isExported(var.getName())) {
escaped.add(var);
}
}
}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> strict mode");
static final DiagnosticType BAD_FUNCTION_DECLARATION = DiagnosticType.error(
"JSC_BAD_FUNCTION_DECLARATION",
"functions can only be declared at top level or immediately within " +
"another function in ES5 strict mode");
private final AbstractCompiler compiler;
private final boolean noVarCheck;
private final boolean noCajaChecks;
StrictModeCheck(AbstractCompiler compiler) {
this(compiler, false, false);
}
StrictModeCheck(
AbstractCompiler compiler, boolean noVarCheck, boolean noCajaChecks) {
this.compiler = compiler;
this.noVarCheck = noVarCheck;
this.noCajaChecks = noCajaChecks;
}
@Override public void process(Node externs, Node root) {
NodeTraversal.traverseRoots(
compiler, Lists.newArrayList(externs, root), this);
NodeTraversal.traverse(compiler, root, new NonExternChecks());
}
@Override public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isFunction()) {
checkFunctionUse(t, n);
} else if (n.isName()) {
if (!isDeclaration(n)) {
checkNameUse(t, n);
}
} else if (n.isAssign()) {
checkAssignment(t, n);
} else if (n.isDelProp()) {
checkDelete(t, n);
} else if (n.isObjectLit()) {
checkObjectLiteral(t, n);
} else if (n.isLabel()) {
checkLabel(t, n);
}
}
/** Checks that the function is used legally. */
private void checkFunctionUse(NodeTraversal t, Node n) {
if (NodeUtil.isFunctionDeclaration(n) && !NodeUtil.isHoistedFunctionDeclaration(n)) {
t.report(n, BAD_FUNCTION_DECLARATION);
}
}
/**
* Determines if the given name is a declaration, which can be a declaration
* of a variable, function, or argument.
*/
private static boolean isDeclaration(Node n) {
switch (n.getParent().getType()) {
case Token.VAR:
case Token.FUNCTION:
case Token.CATCH:
return true;
case Token.PARAM_LIST:
return n.getParent().getParent().isFunction();
default:
return false;
}
}
/** Checks that the given name is used legally. */
private void checkNameUse(NodeTraversal t, Node n) {
Var v = t.getScope().getVar(n.getString());
if (v == null) {
// In particular, this prevents creating a global variable by assigning
// to it without a declaration.
if (!noVarCheck) {
t.report(n, UNKNOWN_VARIABLE, n.getString());
}
}
if (!noCajaChecks) {
if ("eval".equals(n.getString())) {
t.report(n, EVAL_USE);
} else if (n.getString().endsWith("__")) {
t.report(n, ILLEGAL_NAME);
}
}
}
/** Checks that an assignment is not to the "arguments" object. */
private void checkAssignment(NodeTraversal
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> t, Node n) {
if (n.getFirstChild().isName()) {
if ("arguments".equals(n.getFirstChild().getString())) {
t.report(n, ARGUMENTS_ASSIGNMENT);
} else if ("eval".equals(n.getFirstChild().getString())) {
// Note that assignment to eval is already illegal because any use of
// that name is illegal.
if (noCajaChecks) {
t.report(n, EVAL_ASSIGNMENT);
}
}
}
}
/** Checks that variables, functions, and arguments are not deleted. */
private void checkDelete(NodeTraversal t, Node n) {
if (n.getFirstChild().isName()) {
Var v = t.getScope().getVar(n.getFirstChild().getString());
if (v != null) {
t.report(n, DELETE_VARIABLE);
}
}
}
/** Checks that object literal keys are valid. */
private void checkObjectLiteral(NodeTraversal t, Node n) {
Set<String> getters = Sets.newHashSet();
Set<String> setters = Sets.newHashSet();
for (Node key = n.getFirstChild();
key != null;
key = key.getNext()) {
if (!noCajaChecks && key.getString().endsWith("__")) {
t.report(key, ILLEGAL_NAME);
}
if (!key.isSetterDef()) {
// normal property and getter cases
if (getters.contains(key.getString())) {
t.report(key, DUPLICATE_OBJECT_KEY);
} else {
getters.add(key.getString());
}
}
if (!key.isGetterDef()) {
// normal property and setter cases
if (setters.contains(key.getString())) {
t.report(key, DUPLICATE_OBJECT_KEY);
} else {
setters.add(key.getString());
}
}
}
}
/** Checks that label names are valid. */
private void checkLabel(NodeTraversal t, Node n) {
if (n.getFirstChild().getString().endsWith("__")) {
if (!noCajaChecks) {
t.report(n.getFirstChild(), ILLEGAL_NAME);
}
}
}
/** Checks that are performed on non-extern code only. */
private class NonExternChecks extends AbstractPostOrderCallback {
@Override public void visit(NodeTraversal t, Node n, Node parent) {
if ((n.isName()) && isDeclaration(n)) {
checkDeclaration(t, n);
} else if (n.isGetProp()) {
checkProperty(t, n);
}
}
/** Checks for illegal declarations. */
private void checkDeclaration(NodeTraversal t, Node n) {
if ("eval".equals(n.getString())) {
t.report(n, EVAL_DECLARATION);
} else if ("arguments".equals(n.getString())) {
t.report(n, ARGUMENTS_DECLARATION);
} else if (n.getString().endsWith("__")) {
if (!noCajaChecks) {
t.report(n, ILLEGAL_NAME);
}
}
}
/** Checks for illegal property accesses.
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> this.elementsType = new EnumElementType(registry, elementsType, name);
}
/**
* Gets the source node or null if this is an unknown enum.
*/
public Node getSource() {
return source;
}
@Override
public EnumType toMaybeEnumType() {
return this;
}
@Override
public ObjectType getImplicitPrototype() {
return registry.getNativeObjectType(JSTypeNative.OBJECT_TYPE);
}
/**
* Gets the elements defined on this enum.
* @return the elements' names defined on this enum. The returned set is
* immutable.
*/
public Set<String> getElements() {
return Collections.unmodifiableSet(elements);
}
/**
* Defines a new element on this enum.
* @param name the name of the new element
* @param definingNode the {@code Node} that defines this new element
* @return true iff the new element is added successfully
*/
public boolean defineElement(String name, Node definingNode) {
elements.add(name);
return defineDeclaredProperty(name, elementsType, definingNode);
}
/**
* Gets the elements' type.
*/
public EnumElementType getElementsType() {
return elementsType;
}
@Override
public TernaryValue testForEquality(JSType that) {
TernaryValue result = super.testForEquality(that);
if (result != null) {
return result;
}
return this.isEquivalentTo(that) ? TRUE : FALSE;
}
@Override
public boolean isSubtype(JSType that) {
return that.isEquivalentTo(getNativeType(JSTypeNative.OBJECT_TYPE)) ||
that.isEquivalentTo(getNativeType(JSTypeNative.OBJECT_PROTOTYPE)) ||
JSType.isSubtypeHelper(this, that);
}
@Override
String toStringHelper(boolean forAnnotations) {
return forAnnotations ? "Object" : getReferenceName();
}
@Override
public String getDisplayName() {
return elementsType.getDisplayName();
}
@Override
public <T> T visit(Visitor<T> visitor) {
return visitor.caseObjectType(this);
}
@Override <T> T visit(RelationshipVisitor<T> visitor, JSType that) {
return visitor.caseObjectType(this, that);
}
@Override
public FunctionType getConstructor() {
return null;
}
@Override
public boolean matchesNumberContext() {
return false;
}
@Override
public boolean matchesStringContext() {
return true;
}
@Override
public boolean matchesObjectContext() {
return true;
}
@Override
JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) {
elementsType = (EnumElementType) elementsType.resolve(t, scope);
return super.resolveInternal(t, scope);
}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>Type MESSAGE_NOT_INITIALIZED_USING_NEW_SYNTAX =
DiagnosticType.error("JSC_MSG_NOT_INITIALIZED_USING_NEW_SYNTAX",
"message not initialized using " + MSG_FUNCTION_NAME);
static final DiagnosticType BAD_FALLBACK_SYNTAX =
DiagnosticType.error("JSC_MSG_BAD_FALLBACK_SYNTAX",
String.format(
"Bad syntax. " +
"Expected syntax: goog.getMsgWithFallback(MSG_1, MSG_2)",
MSG_FALLBACK_FUNCTION_NAME));
static final DiagnosticType FALLBACK_ARG_ERROR =
DiagnosticType.error("JSC_MSG_FALLBACK_ARG_ERROR",
"Could not find message entry for fallback argument {0}");
private static final String PH_JS_PREFIX = "{$";
private static final String PH_JS_SUFFIX = "}";
static final String MSG_PREFIX = "MSG_";
/**
* Pattern for unnamed messages.
* <p>
* All JS messages in JS code should have unique name but messages in
* generated code (i.e. from soy template) could have duplicated message names.
* Later we replace the message names with ids constructed as a hash of the
* message content.
* <p>
* <link href="http://code.google.com/p/closure-templates/">
* Soy</link> generates messages with names MSG_UNNAMED_<NUMBER> . This
* pattern recognizes such messages.
*/
private static final Pattern MSG_UNNAMED_PATTERN =
Pattern.compile("MSG_UNNAMED_\\d+");
private static final Pattern CAMELCASE_PATTERN =
Pattern.compile("[a-z][a-zA-Z\\d]*[_\\d]*");
static final String HIDDEN_DESC_PREFIX = "@hidden";
// For old-style JS messages
private static final String DESC_SUFFIX = "_HELP";
private final boolean needToCheckDuplications;
private final JsMessage.Style style;
private final JsMessage.IdGenerator idGenerator;
final AbstractCompiler compiler;
/**
* The names encountered associated with their defining node and source. We
* use it for tracking duplicated message ids in the source code.
*/
private final Map<String, MessageLocation> messageNames =
Maps.newHashMap();
private final Map<Var, JsMessage> unnamedMessages =
Maps.newHashMap();
/**
* List of found goog.getMsg call nodes.
*
* When we visit goog.getMsg() node we add a pair node:sourcename and later
* when we visit its parent we remove this pair. All nodes that are left at
* the end of traversing are orphaned nodes. It means have no corresponding
* var or property node.
*/
private final Map<Node, String> googMsgNodes = Maps.newHashMap();
private final CheckLevel checkLevel;
/**
* Creates JS message visitor.
*
* @param compiler the compiler instance
* @param needToCheckDuplications whether to check duplicated messages in
* traversed
* @param style style that should be used during parsing
* @param idGenerator generator that used for creating unique ID for the
* message
*/
JsMessageVisitor(
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>AbstractCompiler compiler,
boolean needToCheckDuplications,
JsMessage.Style style, JsMessage.IdGenerator idGenerator) {
this.compiler = compiler;
this.needToCheckDuplications = needToCheckDuplications;
this.style = style;
this.idGenerator = idGenerator;
checkLevel = (style == JsMessage.Style.CLOSURE)
? CheckLevel.ERROR : CheckLevel.WARNING;
// TODO(anatol): add flag that decides whether to process UNNAMED messages.
// Some projects would not want such functionality (unnamed) as they don't
// use SOY templates.
}
@Override
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, root, this);
for (Map.Entry<Node, String> msgNode : googMsgNodes.entrySet()) {
compiler.report(JSError.make(msgNode.getValue(), msgNode.getKey(),
checkLevel, MESSAGE_NODE_IS_ORPHANED));
}
}
@Override
public void visit(NodeTraversal traversal, Node node, Node parent) {
String messageKey;
boolean isVar;
Node msgNode, msgNodeParent;
switch (node.getType()) {
case Token.NAME:
// var MSG_HELLO = 'Message'
if ((parent != null) && (parent.isVar())) {
messageKey = node.getString();
isVar = true;
} else {
return;
}
msgNode = node.getFirstChild();
msgNodeParent = node;
break;
case Token.ASSIGN:
// somenamespace.someclass.MSG_HELLO = 'Message'
isVar = false;
Node getProp = node.getFirstChild();
if (!getProp.isGetProp()) {
return;
}
Node propNode = getProp.getLastChild();
messageKey = propNode.getString();
msgNode = node.getLastChild();
msgNodeParent = node;
break;
case Token.CALL:
// goog.getMsg()
String fnName = node.getFirstChild().getQualifiedName();
if (MSG_FUNCTION_NAME.equals(fnName)) {
googMsgNodes.put(node, traversal.getSourceName());
} else if (MSG_FALLBACK_FUNCTION_NAME.equals(fnName)) {
visitFallbackFunctionCall(traversal, node);
}
return;
default:
return;
}
// Is this a message name?
boolean isNewStyleMessage =
msgNode != null && msgNode.isCall();
if (!isMessageName(messageKey, isNewStyleMessage)) {
return;
}
if (msgNode == null) {
compiler.report(
traversal.makeError(node, MESSAGE_HAS_NO_VALUE, messageKey));
return;
}
// Just report a warning if a qualified messageKey that looks like a message
// (e.g. "a.b.MSG_X") doesn't use goog.getMsg().
if (isNewStyleMessage) {
googMsgNodes.remove(msgNode);
} else if (style != JsMessage.Style.LEGACY) {
compiler.report(traversal.makeError(node, checkLevel,
MESSAGE_NOT_INITIALIZED_USING
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>_NEW_SYNTAX));
}
boolean isUnnamedMsg = isUnnamedMessageName(messageKey);
Builder builder = new Builder(
isUnnamedMsg ? null : messageKey);
builder.setSourceName(traversal.getSourceName());
try {
if (isVar) {
extractMessageFromVariable(builder, node, parent, parent.getParent());
} else {
extractMessageFromProperty(builder, node.getFirstChild(), node);
}
} catch (MalformedException ex) {
compiler.report(traversal.makeError(ex.getNode(),
MESSAGE_TREE_MALFORMED, ex.getMessage()));
return;
}
JsMessage extractedMessage = builder.build(idGenerator);
// If asked to check named internal messages.
if (needToCheckDuplications
&& !isUnnamedMsg
&& !extractedMessage.isExternal()) {
checkIfMessageDuplicated(messageKey, msgNode);
}
trackMessage(traversal, extractedMessage,
messageKey, msgNode, isUnnamedMsg);
if (extractedMessage.isEmpty()) {
// value of the message is an empty string. Translators do not like it.
compiler.report(traversal.makeError(node, MESSAGE_HAS_NO_TEXT,
messageKey));
}
// New-style messages must have descriptions. We don't emit a warning
// for legacy-style messages, because there are thousands of
// them in legacy code that are not worth the effort to fix, since they've
// already been translated anyway.
String desc = extractedMessage.getDesc();
if (isNewStyleMessage
&& (desc == null || desc.trim().isEmpty())
&& !extractedMessage.isExternal()) {
compiler.report(traversal.makeError(node, MESSAGE_HAS_NO_DESCRIPTION,
messageKey));
}
JsMessageDefinition msgDefinition = new JsMessageDefinition(
node, msgNode, msgNodeParent);
processJsMessage(extractedMessage, msgDefinition);
}
/**
* Track a message for later retrieval.
*
* This is used for tracking duplicates, and for figuring out message
* fallback. Not all message types are trackable, because that would
* require a more sophisticated analysis. e.g.,
* function f(s) { s.MSG_UNNAMED_X = 'Some untrackable message'; }
*/
private void trackMessage(
NodeTraversal t, JsMessage message, String msgName,
Node msgNode, boolean isUnnamedMessage) {
if (!isUnnamedMessage) {
MessageLocation location = new MessageLocation(message, msgNode);
messageNames.put(msgName, location);
} else if (msgNode.isName()) {
Var var = t.getScope().getVar(msgName);
if (var != null) {
unnamedMessages.put(var, message);
}
}
}
/** Get a previously tracked message. */
private JsMessage getTrackedMessage(NodeTraversal t, String msgName) {
boolean isUnnamedMessage = isUnnamedMessageName(msgName);
if (!isUnnamedMessage) {
MessageLocation location = messageNames.get(msgName);
return location == null ? null : location.message;
} else {
Var var = t.getScope().getVar(msgName);
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>
if (var != null) {
return unnamedMessages.get(var);
}
}
return null;
}
/**
* Checks if message already processed. If so - it generates 'message
* duplicated' compiler error.
*
* @param msgName the name of the message
* @param msgNode the node that represents JS message
*/
private void checkIfMessageDuplicated(String msgName, Node msgNode) {
if (messageNames.containsKey(msgName)) {
MessageLocation location = messageNames.get(msgName);
compiler.report(JSError.make(msgNode, MESSAGE_DUPLICATE_KEY,
msgName, location.messageNode.getSourceFileName(),
Integer.toString(location.messageNode.getLineno())));
}
}
/**
* Creates a {@link JsMessage} for a JS message defined using a JS variable
* declaration (e.g <code>var MSG_X = ...;</code>).
*
* @param builder the message builder
* @param nameNode a NAME node for a JS message variable
* @param parentNode a VAR node, parent of {@code nameNode}
* @param grandParentNode the grandparent of {@code nameNode}. This node is
* only used to get meta data about the message that might be
* surrounding it (e.g. a message description). This argument may be
* null if the meta data is not needed.
* @throws MalformedException if {@code varNode} does not
* correspond to a valid JS message VAR node
*/
private void extractMessageFromVariable(
Builder builder, Node nameNode, Node parentNode,
@Nullable Node grandParentNode) throws MalformedException {
// Determine the message's value
Node valueNode = nameNode.getFirstChild();
switch (valueNode.getType()) {
case Token.STRING:
case Token.ADD:
maybeInitMetaDataFromJsDocOrHelpVar(builder, parentNode,
grandParentNode);
builder.appendStringPart(extractStringFromStringExprNode(valueNode));
break;
case Token.FUNCTION:
maybeInitMetaDataFromJsDocOrHelpVar(builder, parentNode,
grandParentNode);
extractFromFunctionNode(builder, valueNode);
break;
case Token.CALL:
maybeInitMetaDataFromJsDoc(builder, parentNode);
extractFromCallNode(builder, valueNode);
break;
default:
throw new MalformedException("Cannot parse value of message "
+ builder.getKey(), valueNode);
}
}
/**
* Creates a {@link JsMessage} for a JS message defined using an assignment to
* a qualified name (e.g <code>a.b.MSG_X = goog.getMsg(...);</code>).
*
* @param builder the message builder
* @param getPropNode a GETPROP node in a JS message assignment
* @param assignNode an ASSIGN node, parent of {@code getPropNode}.
* @throws MalformedException if {@code getPropNode} does not
* correspond to a valid JS message node
*/
private void extractMessageFromProperty(
Builder builder, Node getPropNode, Node assignNode)
throws MalformedException {
Node callNode = getPropNode.getNext();
maybeInitMetaDataFrom
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>JsDoc(builder, assignNode);
extractFromCallNode(builder, callNode);
}
/**
* Initializes the meta data in a JsMessage by examining the nodes just before
* and after a message VAR node.
*
* @param builder the message builder whose meta data will be initialized
* @param varNode the message VAR node
* @param parentOfVarNode {@code varNode}'s parent node
*/
private void maybeInitMetaDataFromJsDocOrHelpVar(
Builder builder, Node varNode, @Nullable Node parentOfVarNode)
throws MalformedException {
// First check description in @desc
if (maybeInitMetaDataFromJsDoc(builder, varNode)) {
return;
}
// Check the preceding node for meta data
if ((parentOfVarNode != null) &&
maybeInitMetaDataFromHelpVar(builder,
parentOfVarNode.getChildBefore(varNode))) {
return;
}
// Check the subsequent node for meta data
maybeInitMetaDataFromHelpVar(builder, varNode.getNext());
}
/**
* Initializes the meta data in a JsMessage by examining a node just before or
* after a message VAR node.
*
* @param builder the message builder whose meta data will be initialized
* @param sibling a node adjacent to the message VAR node
* @return true iff message has corresponding description variable
*/
private boolean maybeInitMetaDataFromHelpVar(Builder builder,
@Nullable Node sibling) throws MalformedException {
if ((sibling != null) && (sibling.isVar())) {
Node nameNode = sibling.getFirstChild();
String name = nameNode.getString();
if (name.equals(builder.getKey() + DESC_SUFFIX)) {
Node valueNode = nameNode.getFirstChild();
String desc = extractStringFromStringExprNode(valueNode);
if (desc.startsWith(HIDDEN_DESC_PREFIX)) {
builder.setDesc(desc.substring(HIDDEN_DESC_PREFIX.length()).trim());
builder.setIsHidden(true);
} else {
builder.setDesc(desc);
}
return true;
}
}
return false;
}
/**
* Initializes the meta data in a message builder given a node that may
* contain JsDoc properties.
*
* @param builder the message builder whose meta data will be initialized
* @param node the node with the message's JSDoc properties
* @return true if message has JsDoc with valid description in @desc
* annotation
*/
private boolean maybeInitMetaDataFromJsDoc(Builder builder, Node node) {
boolean messageHasDesc = false;
JSDocInfo info = node.getJSDocInfo();
if (info != null) {
String desc = info.getDescription();
if (desc != null) {
builder.setDesc(desc);
messageHasDesc = true;
}
if (info.isHidden()) {
builder.setIsHidden(true);
}
if (info.getMeaning() != null) {
builder.setMeaning(info.getMeaning());
}
}
return messageHasDesc;
}
/**
* Returns the string value associated with a node representing a JS string or
* several JS strings added together (e.g. {@code 'str'} or {@
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>code 's' + 't' +
* 'r'}).
*
* @param node the node from where we extract the string
* @return String representation of the node
* @throws MalformedException if the parsed message is invalid
*/
private static String extractStringFromStringExprNode(Node node)
throws MalformedException {
switch (node.getType()) {
case Token.STRING:
return node.getString();
case Token.ADD:
StringBuilder sb = new StringBuilder();
for (Node child : node.children()) {
sb.append(extractStringFromStringExprNode(child));
}
return sb.toString();
default:
throw new MalformedException("STRING or ADD node expected; found: " +
getReadableTokenName(node), node);
}
}
/**
* Initializes a message builder from a FUNCTION node.
* <p>
* <pre>
* The tree should look something like:
*
* function
* |-- name
* |-- lp
* | |-- name <arg1>
* | -- name <arg2>
* -- block
* |
* --return
* |
* --add
* |-- string foo
* -- name <arg1>
* </pre>
*
* @param builder the message builder
* @param node the function node that contains a message
* @throws MalformedException if the parsed message is invalid
*/
private void extractFromFunctionNode(Builder builder, Node node)
throws MalformedException {
Set<String> phNames = Sets.newHashSet();
for (Node fnChild : node.children()) {
switch (fnChild.getType()) {
case Token.NAME:
// This is okay. The function has a name, but it is empty.
break;
case Token.PARAM_LIST:
// Parse the placeholder names from the function argument list.
for (Node argumentNode : fnChild.children()) {
if (argumentNode.isName()) {
String phName = argumentNode.getString();
if (phNames.contains(phName)) {
throw new MalformedException("Duplicate placeholder name: "
+ phName, argumentNode);
} else {
phNames.add(phName);
}
}
}
break;
case Token.BLOCK:
// Build the message's value by examining the return statement
Node returnNode = fnChild.getFirstChild();
if (!returnNode.isReturn()) {
throw new MalformedException("RETURN node expected; found: "
+ getReadableTokenName(returnNode), returnNode);
}
for (Node child : returnNode.children()) {
extractFromReturnDescendant(builder, child);
}
// Check that all placeholders from the message text have appropriate
// object literal keys
for (String phName : builder.getPlaceholders()) {
if (!phNames.contains(phName)) {
throw new MalformedException(
"Unrecognized message placeholder referenced: " + phName,
returnNode);
}
}
break;
default:
throw new MalformedException(
"NAME, LP, or BLOCK node expected; found: "
+ getReadableTokenName(node), fnChild);
}
}
}
/**
* App
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>ends value parts to the message builder by traversing the descendants
* of the given RETURN node.
*
* @param builder the message builder
* @param node the node from where we extract a message
* @throws MalformedException if the parsed message is invalid
*/
private void extractFromReturnDescendant(Builder builder, Node node)
throws MalformedException {
switch (node.getType()) {
case Token.STRING:
builder.appendStringPart(node.getString());
break;
case Token.NAME:
builder.appendPlaceholderReference(node.getString());
break;
case Token.ADD:
for (Node child : node.children()) {
extractFromReturnDescendant(builder, child);
}
break;
default:
throw new MalformedException(
"STRING, NAME, or ADD node expected; found: "
+ getReadableTokenName(node), node);
}
}
/**
* Initializes a message builder from a CALL node.
* <p>
* The tree should look something like:
*
* <pre>
* call
* |-- getprop
* | |-- name 'goog'
* | +-- string 'getMsg'
* |
* |-- string 'Hi {$userName}! Welcome to {$product}.'
* +-- objlit
* |-- string 'userName'
* |-- name 'someUserName'
* |-- string 'product'
* +-- call
* +-- name 'getProductName'
* </pre>
*
* @param builder the message builder
* @param node the call node from where we extract the message
* @throws MalformedException if the parsed message is invalid
*/
private void extractFromCallNode(Builder builder,
Node node) throws MalformedException {
// Check the function being called
if (!node.isCall()) {
throw new MalformedException(
"Message must be initialized using " + MSG_FUNCTION_NAME +
" function.", node);
}
Node fnNameNode = node.getFirstChild();
if (!MSG_FUNCTION_NAME.equals(fnNameNode.getQualifiedName())) {
throw new MalformedException(
"Message initialized using unrecognized function. " +
"Please use " + MSG_FUNCTION_NAME + "() instead.", fnNameNode);
}
// Get the message string
Node stringLiteralNode = fnNameNode.getNext();
if (stringLiteralNode == null) {
throw new MalformedException("Message string literal expected",
stringLiteralNode);
}
// Parse the message string and append parts to the builder
parseMessageTextNode(builder, stringLiteralNode);
Node objLitNode = stringLiteralNode.getNext();
Set<String> phNames = Sets.newHashSet();
if (objLitNode != null) {
// Register the placeholder names
if (!objLitNode.isObjectLit()) {
throw new MalformedException("OBJLIT node expected", objLitNode);
}
for (Node aNode = objLitNode.getFirstChild(); aNode != null;
aNode = aNode.getNext()) {
if (!aNode.isStringKey()) {
throw new MalformedException("STRING_KEY node expected as OBJLIT key",
aNode);
}
String phName =
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> aNode.getString();
if (!isLowerCamelCaseWithNumericSuffixes(phName)) {
throw new MalformedException(
"Placeholder name not in lowerCamelCase: " + phName, aNode);
}
if (phNames.contains(phName)) {
throw new MalformedException("Duplicate placeholder name: "
+ phName, aNode);
}
phNames.add(phName);
}
}
// Check that all placeholders from the message text have appropriate objlit
// values
Set<String> usedPlaceholders = builder.getPlaceholders();
for (String phName : usedPlaceholders) {
if (!phNames.contains(phName)) {
throw new MalformedException(
"Unrecognized message placeholder referenced: " + phName,
objLitNode);
}
}
// Check that objLiteral have only names that are present in the
// message text
for (String phName : phNames) {
if (!usedPlaceholders.contains(phName)) {
throw new MalformedException(
"Unused message placeholder: " + phName,
objLitNode);
}
}
}
/**
* Appends the message parts in a JS message value extracted from the given
* text node.
*
* @param builder the JS message builder to append parts to
* @param node the node with string literal that contains the message text
* @throws MalformedException if {@code value} contains a reference to
* an unregistered placeholder
*/
private void parseMessageTextNode(Builder builder, Node node)
throws MalformedException {
String value = extractStringFromStringExprNode(node);
while (true) {
int phBegin = value.indexOf(PH_JS_PREFIX);
if (phBegin < 0) {
// Just a string literal
builder.appendStringPart(value);
return;
} else {
if (phBegin > 0) {
// A string literal followed by a placeholder
builder.appendStringPart(value.substring(0, phBegin));
}
// A placeholder. Find where it ends
int phEnd = value.indexOf(PH_JS_SUFFIX, phBegin);
if (phEnd < 0) {
throw new MalformedException(
"Placeholder incorrectly formatted in: " + builder.getKey(),
node);
}
String phName = value.substring(phBegin + PH_JS_PREFIX.length(),
phEnd);
builder.appendPlaceholderReference(phName);
int nextPos = phEnd + PH_JS_SUFFIX.length();
if (nextPos < value.length()) {
// Iterate on the rest of the message value
value = value.substring(nextPos);
} else {
// The message is parsed
return;
}
}
}
}
/** Visit a call to goog.getMsgWithFallback. */
private void visitFallbackFunctionCall(NodeTraversal t, Node call) {
// Check to make sure the function call looks like:
// goog.getMsgWithFallback(MSG_1, MSG_2);
if (call.getChildCount() != 3 ||
!call.getChildAtIndex(1).isName() ||
!call.getChildAtIndex(2).isName()) {
compiler.report(t.makeError(call,
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> BAD_FALLBACK_SYNTAX));
return;
}
Node firstArg = call.getChildAtIndex(1);
JsMessage firstMessage = getTrackedMessage(t, firstArg.getString());
if (firstMessage == null) {
compiler.report(
t.makeError(firstArg, FALLBACK_ARG_ERROR, firstArg.getString()));
return;
}
Node secondArg = firstArg.getNext();
JsMessage secondMessage = getTrackedMessage(
t, call.getChildAtIndex(2).getString());
if (secondMessage == null) {
compiler.report(
t.makeError(secondArg, FALLBACK_ARG_ERROR, secondArg.getString()));
return;
}
processMessageFallback(call, firstMessage, secondMessage);
}
/**
* Processes found JS message. Several examples of "standard" processing
* routines are:
* <ol>
* <li>extract all JS messages
* <li>replace JS messages with localized versions for some specific language
* <li>check that messages have correct syntax and present in localization
* bundle
* </ol>
*
* @param message the found message
* @param definition the definition of the object and usually contains all
* additional message information like message node/parent's node
*/
abstract void processJsMessage(JsMessage message,
JsMessageDefinition definition);
/**
* Processes the goog.getMsgWithFallback primitive.
* goog.getMsgWithFallback(MSG_1, MSG_2);
*
* By default, does nothing.
*/
void processMessageFallback(Node callNode, JsMessage message1,
JsMessage message2) {}
/**
* Returns whether the given JS identifier is a valid JS message name.
*/
boolean isMessageName(String identifier, boolean isNewStyleMessage) {
return identifier.startsWith(MSG_PREFIX) &&
(style == JsMessage.Style.CLOSURE || isNewStyleMessage ||
!identifier.endsWith(DESC_SUFFIX));
}
/**
* Returns whether the given message name is in the unnamed namespace.
*/
private static boolean isUnnamedMessageName(String identifier) {
return MSG_UNNAMED_PATTERN.matcher(identifier).matches();
}
/**
* Returns whether a string is nonempty, begins with a lowercase letter, and
* contains only digits and underscores after the first underscore.
*/
static boolean isLowerCamelCaseWithNumericSuffixes(String input) {
return CAMELCASE_PATTERN.matcher(input).matches();
}
/**
* Returns human-readable name of the given node's type.
*/
private static String getReadableTokenName(Node node) {
return Token.name(node.getType());
}
/**
* Converts the given string from upper-underscore case to lower-camel case,
* preserving numeric suffixes. For example: "NAME" -> "name" "A4_LETTER" ->
* "a4Letter" "START_SPAN_1_23" -> "startSpan_1_23".
*/
static String toLowerCamelCaseWithNumericSuffixes(String input) {
// Determine where the numeric suffixes begin
int suffixStart = input.length();
while (suffixStart > 0) {
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> char ch = '\0';
int numberStart = suffixStart;
while (numberStart > 0) {
ch = input.charAt(numberStart - 1);
if (Character.isDigit(ch)) {
numberStart--;
} else {
break;
}
}
if ((numberStart > 0) && (numberStart < suffixStart) && (ch == '_')) {
suffixStart = numberStart - 1;
} else {
break;
}
}
if (suffixStart == input.length()) {
return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, input);
} else {
return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL,
input.substring(0, suffixStart)) +
input.substring(suffixStart);
}
}
/**
* Checks a node's type.
*
* @throws MalformedException if the node is null or the wrong type
*/
protected void checkNode(@Nullable Node node, int type) throws MalformedException {
if (node == null) {
throw new MalformedException(
"Expected node type " + type + "; found: null", node);
}
if (node.getType() != type) {
throw new MalformedException(
"Expected node type " + type + "; found: " + node.getType(), node);
}
}
static class MalformedException extends Exception {
private static final long serialVersionUID = 1L;
private final Node node;
MalformedException(String message, Node node) {
super(message);
this.node = node;
}
Node getNode() {
return node;
}
}
private static class MessageLocation {
private final JsMessage message;
private final Node messageNode;
private MessageLocation(JsMessage message, Node messageNode) {
this.message = message;
this.messageNode = messageNode;
}
}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>
}
}
/**
* Invalidates the given type, so that no properties on it will be renamed.
*/
private void addInvalidatingType(JSType type, JSError error) {
type = type.restrictByNotNullOrUndefined();
if (type.isUnionType()) {
for (JSType alt : type.toMaybeUnionType().getAlternates()) {
addInvalidatingType(alt, error);
}
} else if (type.isEnumElementType()) {
addInvalidatingType(
type.toMaybeEnumElementType().getPrimitiveType(), error);
} else {
typeSystem.addInvalidatingType(type);
recordInvalidationError(type, error);
ObjectType objType = ObjectType.cast(type);
if (objType != null && objType.getImplicitPrototype() != null) {
typeSystem.addInvalidatingType(objType.getImplicitPrototype());
recordInvalidationError(objType.getImplicitPrototype(), error);
}
}
}
/** Returns the property for the given name, creating it if necessary. */
protected Property getProperty(String name) {
if (!properties.containsKey(name)) {
properties.put(name, new Property(name));
}
return properties.get(name);
}
/** Public for testing. */
T getTypeWithProperty(String field, T type) {
return typeSystem.getTypeWithProperty(field, type);
}
/** Tracks the current type system scope while traversing. */
private abstract class AbstractScopingCallback implements ScopedCallback {
protected final Stack<StaticScope<T>> scopes =
new Stack<StaticScope<T>>();
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
return true;
}
@Override
public void enterScope(NodeTraversal t) {
if (t.inGlobalScope()) {
scopes.push(typeSystem.getRootScope());
} else {
scopes.push(typeSystem.getFunctionScope(t.getScopeRoot()));
}
}
@Override
public void exitScope(NodeTraversal t) {
scopes.pop();
}
/** Returns the current scope at this point in the file. */
protected StaticScope<T> getScope() {
return scopes.peek();
}
}
/**
* Finds all properties defined in the externs file and sets them as
* ineligible for renaming from the type on which they are defined.
*/
private class FindExternProperties extends AbstractScopingCallback {
@Override public void visit(NodeTraversal t, Node n, Node parent) {
// TODO(johnlenz): Support object-literal property definitions.
if (n.isGetProp()) {
String field = n.getLastChild().getString();
T type = typeSystem.getType(getScope(), n.getFirstChild(), field);
Property prop = getProperty(field);
if (typeSystem.isInvalidatingType(type)) {
prop.invalidate();
} else {
prop.addTypeToSkip(type);
// If this is a prototype property, then we want to skip assignments
// to the instance type as well. These assignments are not usually
// seen in the extern code itself, so we must handle them here.
if ((type = typeSystem.
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>getInstanceFromPrototype(type)) != null) {
prop.getTypes().add(type);
prop.typesToSkip.add(type);
}
}
}
}
}
/**
* Traverses the tree, building a map from field names to Nodes for all
* fields that can be renamed.
*/
private class FindRenameableProperties extends AbstractScopingCallback {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isGetProp()) {
handleGetProp(t, n);
} else if (n.isObjectLit()) {
handleObjectLit(t, n);
}
}
/**
* Processes a GETPROP node.
*/
private void handleGetProp(NodeTraversal t, Node n) {
String name = n.getLastChild().getString();
T type = typeSystem.getType(getScope(), n.getFirstChild(), name);
Property prop = getProperty(name);
if (!prop.scheduleRenaming(n.getLastChild(),
processProperty(t, prop, type, null))) {
if (propertiesToErrorFor.containsKey(name)) {
String suggestion = "";
if (type instanceof JSType) {
JSType jsType = (JSType) type;
if (jsType.isAllType() || jsType.isUnknownType()) {
if (n.getFirstChild().isThis()) {
suggestion = "The \"this\" object is unknown in the function," +
"consider using @this";
} else {
String qName = n.getFirstChild().getQualifiedName();
suggestion = "Consider casting " + qName +
" if you know it's type.";
}
} else {
List<String> errors = Lists.newArrayList();
printErrorLocations(errors, jsType);
if (!errors.isEmpty()) {
suggestion = "Consider fixing errors for the following types:\n";
suggestion += Joiner.on("\n").join(errors);
}
}
}
compiler.report(JSError.make(
t.getSourceName(), n, propertiesToErrorFor.get(name),
Warnings.INVALIDATION, name,
(type == null ? "null" : type.toString()),
n.toString(), suggestion));
}
}
}
/**
* Processes a OBJECTLIT node.
*/
private void handleObjectLit(NodeTraversal t, Node n) {
Node child = n.getFirstChild();
while (child != null) {
// Maybe STRING, GET, SET
// We should never see a mix of numbers and strings.
String name = child.getString();
T type = typeSystem.getType(getScope(), n, name);
Property prop = getProperty(name);
if (!prop.scheduleRenaming(child,
processProperty(t, prop, type, null))) {
// TODO(user): It doesn't look like the user can do much in this
// case right now.
if (propertiesToErrorFor.containsKey(name)) {
compiler.report(JSError.make(
t.getSourceName(), child, propertiesToErrorFor.get(name),
Warnings.INVALIDATION, name,
(type == null ? "null" : type.toString()), n.toString(), ""));
}
}
child =
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> the case of an unknown JSType, we might need to add in the native
* types since we don't track them, but only if they have the given
* property.
*/
T getType(StaticScope<T> scope, Node node, String prop);
/**
* Returns true if a field reference on this type will invalidate all
* references to that field as candidates for renaming. This is true if the
* type is unknown or all-inclusive, as variables with such a type could be
* references to any object.
*/
boolean isInvalidatingType(T type);
/**
* Informs the given type system that a type is invalidating due to a type
* mismatch found during type checking.
*/
void addInvalidatingType(JSType type);
/**
* Returns a set of types that should be skipped given the given type.
* This is necessary for interfaces when using JSTypes, as all super
* interfaces must also be skipped.
*/
ImmutableSet<T> getTypesToSkipForType(T type);
/**
* Determines whether the given type is one whose properties should not be
* considered for renaming.
*/
boolean isTypeToSkip(T type);
/** Remove null and undefined from the options in the given type. */
T restrictByNotNullOrUndefined(T type);
/**
* Returns the alternatives if this is a type that represents multiple
* types, and null if not. Union and interface types can correspond to
* multiple other types.
*/
Iterable<T> getTypeAlternatives(T type);
/**
* Returns the type in the chain from the given type that contains the given
* field or null if it is not found anywhere.
*/
T getTypeWithProperty(String field, T type);
/**
* Returns the type of the instance of which this is the prototype or null
* if this is not a function prototype.
*/
T getInstanceFromPrototype(T type);
/**
* Records that this property could be referenced from any interface that
* this type, or any type in its superclass chain, implements.
*/
void recordInterfaces(T type, T relatedType,
DisambiguateProperties<T>.Property p);
}
/** Implementation of TypeSystem using JSTypes. */
private static class JSTypeSystem implements TypeSystem<JSType> {
private final Set<JSType> invalidatingTypes;
private JSTypeRegistry registry;
public JSTypeSystem(AbstractCompiler compiler) {
registry = compiler.getTypeRegistry();
invalidatingTypes = Sets.newHashSet(
registry.getNativeType(JSTypeNative.ALL_TYPE),
registry.getNativeType(JSTypeNative.NO_OBJECT_TYPE),
registry.getNativeType(JSTypeNative.NO_TYPE),
registry.getNativeType(JSTypeNative.FUNCTION_PROTOTYPE),
registry.getNativeType(JSTypeNative.FUNCTION_INSTANCE_TYPE),
registry.getNativeType(JSTypeNative.OBJECT_PROTOTYPE),
registry.getNativeType(JSTypeNative.TOP_LEVEL_PROTOTYPE),
registry.getNativeType(JSTypeNative.UNKNOWN_TYPE));
}
@Override public void addInvalidatingType(JSType type) {
checkState(!type.isUnionType());
invalidatingTypes.add(type);
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> }
@Override public StaticScope<JSType> getRootScope() { return null; }
@Override public StaticScope<JSType> getFunctionScope(Node node) {
return null;
}
@Override public JSType getType(
StaticScope<JSType> scope, Node node, String prop) {
if (node.getJSType() == null) {
return registry.getNativeType(JSTypeNative.UNKNOWN_TYPE);
}
return node.getJSType();
}
@Override public boolean isInvalidatingType(JSType type) {
if (type == null || invalidatingTypes.contains(type) ||
type.isUnknownType() /* unresolved types */) {
return true;
}
ObjectType objType = ObjectType.cast(type);
return objType != null && !objType.hasReferenceName();
}
@Override public ImmutableSet<JSType> getTypesToSkipForType(JSType type) {
type = type.restrictByNotNullOrUndefined();
if (type.isUnionType()) {
Set<JSType> types = Sets.newHashSet(type);
for (JSType alt : type.toMaybeUnionType().getAlternates()) {
types.addAll(getTypesToSkipForTypeNonUnion(alt));
}
return ImmutableSet.copyOf(types);
} else if (type.isEnumElementType()) {
return getTypesToSkipForType(
type.toMaybeEnumElementType().getPrimitiveType());
}
return ImmutableSet.copyOf(getTypesToSkipForTypeNonUnion(type));
}
private Set<JSType> getTypesToSkipForTypeNonUnion(JSType type) {
Set<JSType> types = Sets.newHashSet();
JSType skipType = type;
while (skipType != null) {
types.add(skipType);
ObjectType objSkipType = skipType.toObjectType();
if (objSkipType != null) {
skipType = objSkipType.getImplicitPrototype();
} else {
break;
}
}
return types;
}
@Override public boolean isTypeToSkip(JSType type) {
return type.isEnumType() || (type.autoboxesTo() != null);
}
@Override public JSType restrictByNotNullOrUndefined(JSType type) {
return type.restrictByNotNullOrUndefined();
}
@Override public Iterable<JSType> getTypeAlternatives(JSType type) {
if (type.isUnionType()) {
return type.toMaybeUnionType().getAlternates();
} else {
ObjectType objType = type.toObjectType();
if (objType != null &&
objType.getConstructor() != null &&
objType.getConstructor().isInterface()) {
List<JSType> list = Lists.newArrayList();
for (FunctionType impl
: registry.getDirectImplementors(objType)) {
list.add(impl.getInstanceType());
}
return list;
} else {
return null;
}
}
}
@Override public ObjectType getTypeWithProperty(String field, JSType type) {
if (type == null) {
return null;
}
if (type.isEnumElementType()) {
return getTypeWithProperty(
field, type.toMaybeEnumElementType().getPrimitiveType());
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>
if (!(type instanceof ObjectType)) {
if (type.autoboxesTo() != null) {
type = type.autoboxesTo();
} else {
return null;
}
}
// Ignore the prototype itself at all times.
if ("prototype".equals(field)) {
return null;
}
// We look up the prototype chain to find the highest place (if any) that
// this appears. This will make references to overridden properties look
// like references to the initial property, so they are renamed alike.
ObjectType foundType = null;
ObjectType objType = ObjectType.cast(type);
if (objType != null && objType.getConstructor() != null
&& objType.getConstructor().isInterface()) {
ObjectType topInterface = FunctionType.getTopDefiningInterface(
objType, field);
if (topInterface != null && topInterface.getConstructor() != null) {
foundType = topInterface.getConstructor().getPrototype();
}
} else {
while (objType != null && objType.getImplicitPrototype() != objType) {
if (objType.hasOwnProperty(field)) {
foundType = objType;
}
objType = objType.getImplicitPrototype();
}
}
// If the property does not exist on the referenced type but the original
// type is an object type, see if any subtype has the property.
if (foundType == null) {
ObjectType maybeType = ObjectType.cast(
registry.getGreatestSubtypeWithProperty(type, field));
// getGreatestSubtypeWithProperty does not guarantee that the property
// is defined on the returned type, it just indicates that it might be,
// so we have to double check.
if (maybeType != null && maybeType.hasOwnProperty(field)) {
foundType = maybeType;
}
}
return foundType;
}
@Override public JSType getInstanceFromPrototype(JSType type) {
if (type.isFunctionPrototypeType()) {
ObjectType prototype = (ObjectType) type;
FunctionType owner = prototype.getOwnerFunction();
if (owner.isConstructor() || owner.isInterface()) {
return prototype.getOwnerFunction().getInstanceType();
}
}
return null;
}
@Override
public void recordInterfaces(JSType type, JSType relatedType,
DisambiguateProperties<JSType>.Property p) {
ObjectType objType = ObjectType.cast(type);
if (objType != null) {
FunctionType constructor;
if (objType.isFunctionType()) {
constructor = objType.toMaybeFunctionType();
} else if (objType.isFunctionPrototypeType()) {
constructor = objType.getOwnerFunction();
} else {
constructor = objType.getConstructor();
}
while (constructor != null) {
for (ObjectType itype : constructor.getImplementedInterfaces()) {
JSType top = getTypeWithProperty(p.name, itype);
if (top != null) {
p.addType(itype, top, relatedType);
} else {
recordInterfaces(itype, relatedType, p);
}
// If this interface invalidated this property, return now.
if (p.skipRenaming) {
return;
}
}
if (constructor.isInterface() || constructor.
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>isConstructor()) {
constructor = constructor.getSuperClassConstructor();
} else {
constructor = null;
}
}
}
}
}
/** Implementation of TypeSystem using concrete types. */
private static class ConcreteTypeSystem implements TypeSystem<ConcreteType> {
private final TightenTypes tt;
private int nextUniqueId;
private CodingConvention codingConvention;
private final Set<JSType> invalidatingTypes = Sets.newHashSet();
// An array of native types that are not tracked by type tightening, and
// thus need to be added in if an unknown type is encountered.
private static final JSTypeNative [] nativeTypes = new JSTypeNative[] {
JSTypeNative.BOOLEAN_OBJECT_TYPE,
JSTypeNative.NUMBER_OBJECT_TYPE,
JSTypeNative.STRING_OBJECT_TYPE
};
public ConcreteTypeSystem(TightenTypes tt, CodingConvention convention) {
this.tt = tt;
this.codingConvention = convention;
}
@Override public void addInvalidatingType(JSType type) {
checkState(!type.isUnionType());
invalidatingTypes.add(type);
}
@Override public StaticScope<ConcreteType> getRootScope() {
return tt.getTopScope();
}
@Override public StaticScope<ConcreteType> getFunctionScope(Node decl) {
ConcreteFunctionType func = tt.getConcreteFunction(decl);
return (func != null) ?
func.getScope() : (StaticScope<ConcreteType>) null;
}
@Override
public ConcreteType getType(
StaticScope<ConcreteType> scope, Node node, String prop) {
if (scope != null) {
ConcreteType c = tt.inferConcreteType(
(TightenTypes.ConcreteScope) scope, node);
return maybeAddAutoboxes(c, node, prop);
} else {
return null;
}
}
/**
* Add concrete types for autoboxing types if necessary. The concrete type
* system does not track native types, like string, so add them if they are
* present in the JSType for the node.
*/
private ConcreteType maybeAddAutoboxes(
ConcreteType cType, Node node, String prop) {
JSType jsType = node.getJSType();
if (jsType == null) {
return cType;
} else if (jsType.isUnknownType()) {
for (JSTypeNative nativeType : nativeTypes) {
ConcreteType concrete = tt.getConcreteInstance(
tt.getTypeRegistry().getNativeObjectType(nativeType));
if (concrete != null && !concrete.getPropertyType(prop).isNone()) {
cType = cType.unionWith(concrete);
}
}
return cType;
}
return maybeAddAutoboxes(cType, jsType, prop);
}
private ConcreteType maybeAddAutoboxes(
ConcreteType cType, JSType jsType, String prop) {
jsType = jsType.restrictByNotNullOrUndefined();
if (jsType.isUnionType()) {
for (JSType alt : jsType.toMaybeUnionType().getAlternates()) {
cType = maybeAddAutoboxes(cType, alt, prop);
}
return cType;
} else if (jsType.isEnum
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
/**
* Check for usage of 'with'.
*
*/
class ControlStructureCheck implements HotSwapCompilerPass {
private final AbstractCompiler compiler;
static final DiagnosticType USE_OF_WITH = DiagnosticType.warning(
"JSC_USE_OF_WITH",
"The use of the 'with' structure should be avoided.");
ControlStructureCheck(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void process(Node externs, Node root) {
check(root);
}
@Override
public void hotSwapScript(Node scriptRoot, Node originalRoot) {
check(scriptRoot);
}
/**
* Reports errors for any invalid use of control structures.
*
* @param node Current node to check.
*/
private void check(Node node) {
switch (node.getType()) {
case Token.WITH:
JSDocInfo info = node.getJSDocInfo();
boolean allowWith =
info != null && info.getSuppressions().contains("with");
if (!allowWith) {
report(node, USE_OF_WITH);
}
break;
}
for (Node bChild = node.getFirstChild(); bChild != null;) {
Node next = bChild.getNext();
check(bChild);
bChild = next;
}
}
private void report(Node n, DiagnosticType error) {
compiler.report(JSError.make(n.getSourceFileName(), n, error));
}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> {
this.referencedObjType = null;
}
}
@Override
public String getReferenceName() {
return referencedObjType == null ?
"" : referencedObjType.getReferenceName();
}
@Override
public boolean hasReferenceName() {
return referencedObjType == null ?
null : referencedObjType.hasReferenceName();
}
@Override
public boolean matchesNumberContext() {
return referencedType.matchesNumberContext();
}
@Override
public boolean matchesStringContext() {
return referencedType.matchesStringContext();
}
@Override
public boolean matchesObjectContext() {
return referencedType.matchesObjectContext();
}
@Override
public boolean canBeCalled() {
return referencedType.canBeCalled();
}
@Override
public boolean isNoType() {
return referencedType.isNoType();
}
@Override
public boolean isNoObjectType() {
return referencedType.isNoObjectType();
}
@Override
public boolean isNoResolvedType() {
return referencedType.isNoResolvedType();
}
@Override
public boolean isUnknownType() {
return referencedType.isUnknownType();
}
@Override
public boolean isCheckedUnknownType() {
return referencedType.isCheckedUnknownType();
}
@Override
public boolean isNullable() {
return referencedType.isNullable();
}
@Override
public EnumType toMaybeEnumType() {
return referencedType.toMaybeEnumType();
}
@Override
public boolean isConstructor() {
return referencedType.isConstructor();
}
@Override
public boolean isNominalType() {
return referencedType.isNominalType();
}
@Override
public boolean isInstanceType() {
return referencedType.isInstanceType();
}
@Override
public boolean isInterface() {
return referencedType.isInterface();
}
@Override
public boolean isOrdinaryFunction() {
return referencedType.isOrdinaryFunction();
}
@Override
public boolean isAllType() {
return referencedType.isAllType();
}
@Override
public boolean isStruct() {
return referencedType.isStruct();
}
@Override
public boolean isDict() {
return referencedType.isDict();
}
@Override
public boolean isNativeObjectType() {
return referencedObjType == null
? false : referencedObjType.isNativeObjectType();
}
@Override
RecordType toMaybeRecordType() {
return referencedType.toMaybeRecordType();
}
@Override
public UnionType toMaybeUnionType() {
return referencedType.toMaybeUnionType();
}
@Override
public FunctionType toMaybeFunctionType() {
return referencedType.toMaybeFunctionType();
}
@Override
public EnumElementType toMaybeEnumElementType() {
return referencedType.toMaybeEnumElementType();
}
@Override
public TernaryValue testForEquality(JSType that) {
return referencedType.testForEquality(that);
}
@Override
public boolean isSubtype(JSType that) {
return referencedType.isSubtype(that);
}
@Override
public FunctionType getOwnerFunction() {
return referencedObjType == null
? null : referencedObjType.getOwnerFunction();
}
@Override
public Iterable<ObjectType> getCtorImplementedInterfaces() {
return referencedObjType == null ?
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> Collections.<ObjectType>emptyList() :
referencedObjType.getCtorImplementedInterfaces();
}
@Override
public int hashCode() {
return referencedType.hashCode();
}
@Override
String toStringHelper(boolean forAnnotations) {
return referencedType.toStringHelper(forAnnotations);
}
@Override
public ObjectType getImplicitPrototype() {
return referencedObjType == null ? null :
referencedObjType.getImplicitPrototype();
}
@Override
boolean defineProperty(String propertyName, JSType type,
boolean inferred, Node propertyNode) {
return referencedObjType == null ? true :
referencedObjType.defineProperty(
propertyName, type, inferred, propertyNode);
}
@Override
public boolean removeProperty(String name) {
return referencedObjType == null ? false :
referencedObjType.removeProperty(name);
}
@Override
public JSType findPropertyType(String propertyName) {
return referencedType.findPropertyType(propertyName);
}
@Override
public JSDocInfo getJSDocInfo() {
return referencedType.getJSDocInfo();
}
@Override
public void setJSDocInfo(JSDocInfo info) {
if (referencedObjType != null) {
referencedObjType.setJSDocInfo(info);
}
}
@Override
public void setPropertyJSDocInfo(String propertyName, JSDocInfo info) {
if (referencedObjType != null) {
referencedObjType.setPropertyJSDocInfo(propertyName, info);
}
}
@Override
public FunctionType getConstructor() {
return referencedObjType == null ? null :
referencedObjType.getConstructor();
}
@Override
public ImmutableList<JSType> getTemplateTypes() {
return referencedObjType == null ? null :
referencedObjType.getTemplateTypes();
}
@Override
public <T> T visit(Visitor<T> visitor) {
return referencedType.visit(visitor);
}
@Override <T> T visit(RelationshipVisitor<T> visitor, JSType that) {
return referencedType.visit(visitor, that);
}
@Override
JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) {
setReferencedType(referencedType.resolve(t, scope));
return this;
}
@Override
public String toDebugHashCodeString() {
return "{proxy:" + referencedType.toDebugHashCodeString() + "}";
}
@Override
public JSType getTypeOfThis() {
if (referencedObjType != null) {
return referencedObjType.getTypeOfThis();
}
return super.getTypeOfThis();
}
@Override
public JSType collapseUnion() {
if (referencedType.isUnionType()) {
return referencedType.collapseUnion();
}
return this;
}
@Override
public void matchConstraint(JSType constraint) {
referencedType.matchConstraint(constraint);
}
@Override
public TemplatizedType toMaybeTemplatizedType() {
return referencedType.toMaybeTemplatizedType();
}
@Override
public TemplateType toMaybeTemplateType() {
return referencedType.toMaybeTemplateType();
}
@Override
public boolean hasAnyTemplateTypesInternal() {
return referencedType.hasAnyTemplateTypes();
}
@Override
public TemplateTypeMap getTemplateTypeMap() {
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>
/**
* Gets the type of the class that "owns" a method, or null if
* we know that its un-owned.
*/
private JSType getClassOfMethod(Node n, Node parent) {
if (parent.isAssign()) {
Node lValue = parent.getFirstChild();
if (NodeUtil.isGet(lValue)) {
// We have an assignment of the form "a.b = ...".
JSType lValueType = lValue.getJSType();
if (lValueType != null && lValueType.isNominalConstructor()) {
// If a.b is a constructor, then everything in this function
// belongs to the "a.b" type.
return (lValueType.toMaybeFunctionType()).getInstanceType();
} else {
// If a.b is not a constructor, then treat this as a method
// of whatever type is on "a".
return normalizeClassType(lValue.getFirstChild().getJSType());
}
} else {
// We have an assignment of the form "a = ...", so pull the
// type off the "a".
return normalizeClassType(lValue.getJSType());
}
} else if (NodeUtil.isFunctionDeclaration(n) ||
parent.isName()) {
return normalizeClassType(n.getJSType());
}
return null;
}
/**
* Normalize the type of a constructor, its instance, and its prototype
* all down to the same type (the instance type).
*/
private JSType normalizeClassType(JSType type) {
if (type == null || type.isUnknownType()) {
return type;
} else if (type.isNominalConstructor()) {
return (type.toMaybeFunctionType()).getInstanceType();
} else if (type.isFunctionPrototypeType()) {
FunctionType owner = ((ObjectType) type).getOwnerFunction();
if (owner.isConstructor()) {
return owner.getInstanceType();
}
}
return type;
}
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.NAME:
checkNameDeprecation(t, n, parent);
checkNameVisibility(t, n, parent);
break;
case Token.GETPROP:
checkPropertyDeprecation(t, n, parent);
checkPropertyVisibility(t, n, parent);
checkConstantProperty(t, n);
break;
case Token.NEW:
checkConstructorDeprecation(t, n, parent);
break;
case Token.FUNCTION:
checkFinalClassOverrides(t, n, parent);
break;
}
}
/**
* Checks the given NEW node to ensure that access restrictions are obeyed.
*/
private void checkConstructorDeprecation(NodeTraversal t, Node n,
Node parent) {
JSType type = n.getJSType();
if (type != null) {
String deprecationInfo = getTypeDeprecationInfo(type);
if (deprecationInfo != null &&
shouldEmitDeprecationWarning(t, n, parent)) {
if (!deprecationInfo.isEmpty()) {
compiler.report(
t.makeError(n,
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> DEPRECATED_CLASS_REASON,
type.toString(), deprecationInfo));
} else {
compiler.report(
t.makeError(n, DEPRECATED_CLASS, type.toString()));
}
}
}
}
/**
* Checks the given NAME node to ensure that access restrictions are obeyed.
*/
private void checkNameDeprecation(NodeTraversal t, Node n, Node parent) {
// Don't bother checking definitions or constructors.
if (parent.isFunction() || parent.isVar() ||
parent.isNew()) {
return;
}
Scope.Var var = t.getScope().getVar(n.getString());
JSDocInfo docInfo = var == null ? null : var.getJSDocInfo();
if (docInfo != null && docInfo.isDeprecated() &&
shouldEmitDeprecationWarning(t, n, parent)) {
if (docInfo.getDeprecationReason() != null) {
compiler.report(
t.makeError(n, DEPRECATED_NAME_REASON, n.getString(),
docInfo.getDeprecationReason()));
} else {
compiler.report(
t.makeError(n, DEPRECATED_NAME, n.getString()));
}
}
}
/**
* Checks the given GETPROP node to ensure that access restrictions are
* obeyed.
*/
private void checkPropertyDeprecation(NodeTraversal t, Node n, Node parent) {
// Don't bother checking constructors.
if (parent.isNew()) {
return;
}
ObjectType objectType =
ObjectType.cast(dereference(n.getFirstChild().getJSType()));
String propertyName = n.getLastChild().getString();
if (objectType != null) {
String deprecationInfo
= getPropertyDeprecationInfo(objectType, propertyName);
if (deprecationInfo != null &&
shouldEmitDeprecationWarning(t, n, parent)) {
if (!deprecationInfo.isEmpty()) {
compiler.report(
t.makeError(n, DEPRECATED_PROP_REASON, propertyName,
validator.getReadableJSTypeName(n.getFirstChild(), true),
deprecationInfo));
} else {
compiler.report(
t.makeError(n, DEPRECATED_PROP, propertyName,
validator.getReadableJSTypeName(n.getFirstChild(), true)));
}
}
}
}
/**
* Determines whether the given name is visible in the current context.
* @param t The current traversal.
* @param name The name node.
*/
private void checkNameVisibility(NodeTraversal t, Node name, Node parent) {
Var var = t.getScope().getVar(name.getString());
if (var != null) {
JSDocInfo docInfo = var.getJSDocInfo();
if (docInfo != null) {
// If a name is private, make sure that we're in the same file.
Visibility visibility = docInfo.getVisibility();
if (visibility == Visibility.PRIVATE) {
StaticSourceFile varSrc = var.getSourceFile();
StaticSourceFile refSrc = name.getStaticSourceFile();
if (varSrc != null &&
refSrc != null &&
!varSrc.getName().equals(refSrc.getName())) {
if (docInfo.isConstructor() &&
isValidPrivateConstructorAccess(parent))
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> {
return;
}
compiler.report(
t.makeError(name, BAD_PRIVATE_GLOBAL_ACCESS,
name.getString(), varSrc.getName()));
}
}
}
}
}
/**
* Checks if a constructor is trying to override a final class.
*/
private void checkFinalClassOverrides(NodeTraversal t, Node fn, Node parent) {
JSType type = fn.getJSType().toMaybeFunctionType();
if (type != null && type.isConstructor()) {
JSType finalParentClass = getFinalParentClass(getClassOfMethod(fn, parent));
if (finalParentClass != null) {
compiler.report(
t.makeError(fn, EXTEND_FINAL_CLASS,
type.getDisplayName(), finalParentClass.getDisplayName()));
}
}
}
/**
* Determines whether the given property with @const tag got reassigned
* @param t The current traversal.
* @param getprop The getprop node.
*/
private void checkConstantProperty(NodeTraversal t,
Node getprop) {
// Check whether the property is modified
Node parent = getprop.getParent();
boolean isDelete = parent.isDelProp();
if (!(NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == getprop)
&& !parent.isInc() && !parent.isDec()
&& !isDelete) {
return;
}
ObjectType objectType =
ObjectType.cast(dereference(getprop.getFirstChild().getJSType()));
String propertyName = getprop.getLastChild().getString();
boolean isConstant = isPropertyDeclaredConstant(objectType, propertyName);
// Check whether constant properties are reassigned
if (isConstant) {
if (isDelete) {
compiler.report(
t.makeError(getprop, CONST_PROPERTY_DELETED, propertyName));
return;
}
ObjectType oType = objectType;
while (oType != null) {
if (oType.hasReferenceName()) {
if (initializedConstantProperties.containsEntry(
oType.getReferenceName(), propertyName)) {
compiler.report(
t.makeError(getprop, CONST_PROPERTY_REASSIGNED_VALUE,
propertyName));
break;
}
}
oType = oType.getImplicitPrototype();
}
Preconditions.checkState(objectType.hasReferenceName());
initializedConstantProperties.put(objectType.getReferenceName(),
propertyName);
// Add the prototype when we're looking at an instance object
if (objectType.isInstanceType()) {
ObjectType prototype = objectType.getImplicitPrototype();
if (prototype != null) {
if (prototype.hasProperty(propertyName)
&& prototype.hasReferenceName()) {
initializedConstantProperties.put(prototype.getReferenceName(),
propertyName);
}
}
}
}
}
/**
* Determines whether the given property is visible in the current context.
* @param t The current traversal.
* @param getprop The getprop node.
*/
private void checkPropertyVisibility(NodeTraversal t,
Node getprop, Node parent) {
ObjectType objectType =
ObjectType.cast(dereference(getprop.getFirstChild().getJSType()));
String propertyName = getprop.getLastChild().getString();
if (objectType !=
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> null) {
// Is this a normal property access, or are we trying to override
// an existing property?
boolean isOverride = parent.getJSDocInfo() != null &&
parent.isAssign() &&
parent.getFirstChild() == getprop;
// Find the lowest property defined on a class with visibility
// information.
if (isOverride) {
objectType = objectType.getImplicitPrototype();
}
JSDocInfo docInfo = null;
for (; objectType != null;
objectType = objectType.getImplicitPrototype()) {
docInfo = objectType.getOwnPropertyJSDocInfo(propertyName);
if (docInfo != null &&
docInfo.getVisibility() != Visibility.INHERITED) {
break;
}
}
if (objectType == null) {
// We couldn't find a visibility modifier; assume it's public.
return;
}
String referenceSource = getprop.getSourceFileName();
String definingSource = docInfo.getSourceName();
boolean sameInput = referenceSource != null
&& referenceSource.equals(definingSource);
Visibility visibility = docInfo.getVisibility();
JSType ownerType = normalizeClassType(objectType);
if (isOverride) {
// Check an ASSIGN statement that's trying to override a property
// on a superclass.
JSDocInfo overridingInfo = parent.getJSDocInfo();
Visibility overridingVisibility = overridingInfo == null ?
Visibility.INHERITED : overridingInfo.getVisibility();
// Check that (a) the property *can* be overridden, and
// (b) that the visibility of the override is the same as the
// visibility of the original property.
if (visibility == Visibility.PRIVATE && !sameInput) {
compiler.report(
t.makeError(getprop, PRIVATE_OVERRIDE,
objectType.toString()));
} else if (overridingVisibility != Visibility.INHERITED &&
overridingVisibility != visibility) {
compiler.report(
t.makeError(getprop, VISIBILITY_MISMATCH,
visibility.name(), objectType.toString(),
overridingVisibility.name()));
}
} else {
if (sameInput) {
// private access is always allowed in the same file.
return;
} else if (visibility == Visibility.PRIVATE &&
(currentClass == null || !ownerType.isEquivalentTo(currentClass))) {
if (docInfo.isConstructor() &&
isValidPrivateConstructorAccess(parent)) {
return;
}
// private access is not allowed outside the file from a different
// enclosing class.
compiler.report(
t.makeError(getprop,
BAD_PRIVATE_PROPERTY_ACCESS,
propertyName,
validator.getReadableJSTypeName(
getprop.getFirstChild(), true)));
} else if (visibility == Visibility.PROTECTED) {
// There are 3 types of legal accesses of a protected property:
// 1) Accesses in the same file
// 2) Overriding the property in a subclass
// 3) Accessing the property from inside a subclass
// The first two have already been checked for.
if (currentClass == null || !currentClass.isSubtype(ownerType)) {
compiler.report(
t.makeError(getprop, BAD_PROTECTED
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> of the class with the singleton getter. By default, always
* returns null. Meant to be overridden by subclasses.
*
* addSingletonGetter needs a coding convention because in the general case,
* it can't be inlined. The function inliner sees that it creates an alias
* to the given class in an inner closure, and bails out.
*
* @param callNode A CALL node.
*/
public String getSingletonGetterClassName(Node callNode);
/**
* In many JS libraries, the function that adds a singleton getter to a class
* adds properties to the class.
*/
public void applySingletonGetter(FunctionType functionType,
FunctionType getterType, ObjectType objectType);
/**
* @return Whether the function is inlinable by convention.
*/
public boolean isInlinableFunction(Node n);
/**
* @return the delegate relationship created by the call or null.
*/
public DelegateRelationship getDelegateRelationship(Node callNode);
/**
* In many JS libraries, the function that creates a delegate relationship
* also adds properties to the delegator and delegate base.
*/
public void applyDelegateRelationship(
ObjectType delegateSuperclass, ObjectType delegateBase,
ObjectType delegator, FunctionType delegateProxy,
FunctionType findDelegate);
/**
* @return the name of the delegate superclass.
*/
public String getDelegateSuperclassName();
/**
* Checks for function calls that set the calling conventions on delegate
* methods.
*/
public void checkForCallingConventionDefiningCalls(
Node n, Map<String, String> delegateCallingConventions);
/**
* Defines the delegate proxy prototype properties. Their types depend on
* properties of the delegate base methods.
*
* @param delegateProxyPrototypes List of delegate proxy prototypes.
*/
public void defineDelegateProxyPrototypeProperties(
JSTypeRegistry registry, StaticScope<JSType> scope,
List<ObjectType> delegateProxyPrototypes,
Map<String, String> delegateCallingConventions);
/**
* Gets the name of the global object.
*/
public String getGlobalObject();
/**
* A Bind instance or null.
*/
public Bind describeFunctionBind(Node n);
/**
* A Bind instance or null.
* @param useTypeInfo If we believe type information is reliable enough
* to use to figure out what the bind function is.
*/
public Bind describeFunctionBind(Node n, boolean useTypeInfo);
/** Bind class */
public static class Bind {
// The target of the bind action
final Node target;
// The node representing the "this" value, maybe null
final Node thisValue;
// The head of a Node list representing the parameters
final Node parameters;
public Bind(Node target, Node thisValue, Node parameters) {
this.target = target;
this.thisValue = thisValue;
this.parameters = parameters;
}
/**
* The number of parameters bound (not including the 'this' value).
*/
int getBoundParameterCount() {
if (parameters == null) {
return 0;
}
Node paramParent = parameters.getParent();
return paramParent.getChildCount() -
paramParent.getIndexOfChild(parameters);
}
}
/**
* Whether this CALL function is testing for the existence of a property.
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>Inequality(JSType that) {
UnionTypeBuilder thisRestricted = new UnionTypeBuilder(registry);
UnionTypeBuilder thatRestricted = new UnionTypeBuilder(registry);
for (JSType element : alternates) {
TypePair p = element.getTypesUnderInequality(that);
if (p.typeA != null) {
thisRestricted.addAlternate(p.typeA);
}
if (p.typeB != null) {
thatRestricted.addAlternate(p.typeB);
}
}
return new TypePair(
thisRestricted.build(),
thatRestricted.build());
}
@Override
public TypePair getTypesUnderShallowInequality(JSType that) {
UnionTypeBuilder thisRestricted = new UnionTypeBuilder(registry);
UnionTypeBuilder thatRestricted = new UnionTypeBuilder(registry);
for (JSType element : alternates) {
TypePair p = element.getTypesUnderShallowInequality(that);
if (p.typeA != null) {
thisRestricted.addAlternate(p.typeA);
}
if (p.typeB != null) {
thatRestricted.addAlternate(p.typeB);
}
}
return new TypePair(
thisRestricted.build(),
thatRestricted.build());
}
@Override
public <T> T visit(Visitor<T> visitor) {
return visitor.caseUnionType(this);
}
@Override <T> T visit(RelationshipVisitor<T> visitor, JSType that) {
return visitor.caseUnionType(this, that);
}
@Override
JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) {
setResolvedTypeInternal(this); // for circularly defined types.
// Just resolve the alternates, but do not update as that breaks some error
// reporting cases.
for (JSType alternate : alternates) {
alternate.resolve(t, scope);
}
// Ensure the union is in a normalized state.
rebuildAlternates();
return this;
}
@Override
public String toDebugHashCodeString() {
List<String> hashCodes = Lists.newArrayList();
for (JSType a : alternates) {
hashCodes.add(a.toDebugHashCodeString());
}
return "{(" + Joiner.on(",").join(hashCodes) + ")}";
}
@Override
public boolean setValidator(Predicate<JSType> validator) {
for (JSType a : alternates) {
a.setValidator(validator);
}
return true;
}
@Override
public JSType collapseUnion() {
JSType currentValue = null;
ObjectType currentCommonSuper = null;
for (JSType a : alternates) {
if (a.isUnknownType()) {
return getNativeType(JSTypeNative.UNKNOWN_TYPE);
}
ObjectType obj = a.toObjectType();
if (obj == null) {
if (currentValue == null && currentCommonSuper == null) {
// If obj is not an object, then it must be a value.
currentValue = a;
} else {
// Multiple values and objects will always collapse to the ALL_TYPE.
return getNativeType(JSTypeNative.ALL_TYPE);
}
} else if
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> public boolean isSubtype(JSType that) {
if (JSType.isSubtypeHelper(this, that)) {
return true;
} else {
return that.isObject() && !that.isNoType() && !that.isNoResolvedType();
}
}
@Override
public FunctionType toMaybeFunctionType() {
return null;
}
@Override
public boolean isNoObjectType() {
return true;
}
@Override
public ObjectType getImplicitPrototype() {
return null;
}
@Override
public String getReferenceName() {
return null;
}
@Override
public boolean matchesNumberContext() {
return true;
}
@Override
public boolean matchesObjectContext() {
return true;
}
@Override
public boolean matchesStringContext() {
return true;
}
@Override
public int hashCode() {
return System.identityHashCode(this);
}
@Override
boolean defineProperty(String propertyName, JSType type,
boolean inferred, Node propertyNode) {
// nothing, all properties are defined
return true;
}
@Override
public boolean removeProperty(String name) {
return false;
}
@Override
public void setPropertyJSDocInfo(String propertyName, JSDocInfo info) {
// Do nothing, specific properties do not have JSDocInfo.
}
@Override
public <T> T visit(Visitor<T> visitor) {
return visitor.caseNoObjectType();
}
@Override <T> T visit(RelationshipVisitor<T> visitor, JSType that) {
return visitor.caseNoObjectType(that);
}
@Override
String toStringHelper(boolean forAnnotations) {
return forAnnotations ? "?" : "NoObject";
}
@Override
public FunctionType getConstructor() {
return null;
}
@Override
JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) {
return this;
}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>ilineText, token);
}
builder.append(toString(token));
line = getRemainingJSDocLine();
if (option != WhitespaceOption.PRESERVE) {
line = trimEnd(line);
}
builder.append(line);
token = next();
}
} while (true);
}
/**
* Extracts the top-level block comment from the JsDoc comment, if any.
* This method differs from the extractMultilineTextualBlock in that it
* terminates under different conditions (it doesn't have the same
* prechecks), it does not first read in the remaining of the current
* line and its conditions for ignoring the "*" (STAR) are different.
*
* @param token The starting token.
*
* @return The extraction information.
*/
private ExtractionInfo extractBlockComment(JsDocToken token) {
StringBuilder builder = new StringBuilder();
boolean ignoreStar = true;
do {
switch (token) {
case ANNOTATION:
case EOC:
case EOF:
return new ExtractionInfo(builder.toString().trim(), token);
case STAR:
if (!ignoreStar) {
if (builder.length() > 0) {
builder.append(' ');
}
builder.append('*');
}
token = next();
continue;
case EOL:
ignoreStar = true;
builder.append('\n');
token = next();
continue;
default:
if (!ignoreStar && builder.length() > 0) {
builder.append(' ');
}
ignoreStar = false;
builder.append(toString(token));
String line = getRemainingJSDocLine();
line = trimEnd(line);
builder.append(line);
token = next();
}
} while (true);
}
/**
* Trim characters from only the end of a string.
* This method will remove all whitespace characters
* (defined by Character.isWhitespace(char), in addition to the characters
* provided, from the end of the provided string.
*
* @param s String to be trimmed
* @return String with whitespace and characters in extraChars removed
* from the end.
*/
private static String trimEnd(String s) {
int trimCount = 0;
while (trimCount < s.length()) {
char ch = s.charAt(s.length() - trimCount - 1);
if (Character.isWhitespace(ch)) {
trimCount++;
} else {
break;
}
}
if (trimCount == 0) {
return s;
}
return s.substring(0, s.length() - trimCount);
}
// Based on ES4 grammar proposed on July 10, 2008.
// http://wiki.ecmascript.org/doku.php?id=spec:spec
// Deliberately written to line up with the actual grammar rules,
// for maximum flexibility.
// TODO(nicksantos): The current implementation tries to maintain backwards
// compatibility with previous versions of the spec whenever we can.
// We should try to gradually withdraw support for these.
/**
* TypeExpressionAnnotation := TypeExpression |
* '{' TopLevelTypeExpression '}'
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>/*
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Rhino code, released
* May 6, 1999.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1997-1999
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Bob Jervis
* Google Inc.
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU General Public License Version 2 or later (the "GPL"), in which
* case the provisions of the GPL are applicable instead of those above. If
* you wish to allow use of your version of this file only under the terms of
* the GPL and not to allow others to use your version of this file under the
* MPL, indicate your decision by deleting the provisions above and replacing
* them with the notice and other provisions required by the GPL. If you do
* not delete the provisions above, a recipient may use your version of this
* file under either the MPL or the GPL.
*
* ***** END LICENSE BLOCK ***** */
package com.google.javascript.rhino.jstype;
import com.google.javascript.rhino.JSDocInfo;
/**
* The {@code StaticSlot} interface must be implemented by variables that can
* appear as members of a {@code StaticScope}.
*
* @param <T> The type of information stored about the slot
*/
public interface StaticSlot<T> {
/**
* Gets the name of the slot.
*/
String getName();
/**
* Returns the type information, if any, for this slot.
* @return The type or {@code null} if no type is declared for it.
*/
T getType();
/**
* Returns whether the type has been inferred (as opposed to declared).
*/
boolean isTypeInferred();
/** Gets the declaration of this symbol. May not exist. */
StaticReference<T> getDeclaration();
/** Gets the JSDoc for this slot. */
JSDocInfo getJSDocInfo();
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>/*
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Rhino code, released
* May 6, 1999.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1997-1999
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* John Lenz
* Google Inc.
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU General Public License Version 2 or later (the "GPL"), in which
* case the provisions of the GPL are applicable instead of those above. If
* you wish to allow use of your version of this file only under the terms of
* the GPL and not to allow others to use your version of this file under the
* MPL, indicate your decision by deleting the provisions above and replacing
* them with the notice and other provisions required by the GPL. If you do
* not delete the provisions above, a recipient may use your version of this
* file under either the MPL or the GPL.
*
* ***** END LICENSE BLOCK ***** */
package com.google.javascript.rhino.jstype;
/**
* A "can cast to" relationship visitor.
*/
class CanCastToVisitor implements RelationshipVisitor<Boolean> {
@Override
public Boolean caseUnknownType(JSType thisType, JSType thatType) {
return true;
}
@Override
public Boolean caseNoType(JSType thatType) {
return true;
}
@Override
public Boolean caseNoObjectType(JSType thatType) {
return true; // TODO(johnlenz): restrict to objects
}
@Override
public Boolean caseAllType(JSType thatType) {
return true;
}
boolean canCastToUnion(JSType thisType, UnionType unionType) {
for (JSType type : unionType.getAlternates()) {
if (thisType.visit(this, type)) {
return true;
}
}
return false;
}
boolean canCastToFunction(JSType thisType, FunctionType functionType) {
if (thisType.isFunctionType()) {
// TODO(johnlenz): visit function parts
return true;
} else {
return thisType.isSubtype(functionType)
|| functionType.isSubtype(thisType);
}
}
private boolean isInterface(JSType type) {
ObjectType objType = type.toObjectType();
if (objType != null) {
JSType constructor = objType.getConstructor();
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>
return constructor != null && constructor.isInterface();
}
return false;
}
Boolean castCastToHelper(JSType thisType, JSType thatType) {
if (thatType.isUnknownType()
|| thatType.isAllType()
|| thatType.isNoObjectType() // TODO(johnlenz): restrict to objects
|| thatType.isNoType()) {
return true;
} else if (thisType.isRecordType() || thatType.isRecordType()) {
return true; // TODO(johnlenz): are there any misuses we can catch?
} else if (isInterface(thisType) || isInterface(thatType)) {
return true; // TODO(johnlenz): are there any misuses we can catch?
} else if (thatType.isEnumElementType()) {
return thisType.visit(this,
thatType.toMaybeEnumElementType().getPrimitiveType());
} else if (thatType.isUnionType()) {
return canCastToUnion(thisType, thatType.toMaybeUnionType());
} else if (thatType.isFunctionType()) {
return canCastToFunction(thisType, thatType.toMaybeFunctionType());
} else if (thatType.isTemplatizedType()) {
// TODO(johnlenz): once the templated type work is finished,
// restrict the type parameters.
return thisType.visit(this,
thatType.toMaybeTemplatizedType().getReferencedTypeInternal());
}
return thisType.isSubtype(thatType) || thatType.isSubtype(thisType);
}
@Override
public Boolean caseValueType(ValueType thisType, JSType thatType) {
return castCastToHelper(thisType, thatType);
}
@Override
public Boolean caseObjectType(ObjectType thisType, JSType thatType) {
return castCastToHelper(thisType, thatType);
}
@Override
public Boolean caseFunctionType(FunctionType thisType, JSType thatType) {
return castCastToHelper(thisType, thatType);
}
@Override
public Boolean caseUnionType(UnionType thisType, JSType thatType) {
boolean visited = false;
for (JSType type : thisType.getAlternates()) {
if (type.isVoidType() || type.isNullType()) {
// Don't allow if the only match between the types is null or void,
// otherwise any nullable type would be castable to any other nullable
// type and we don't want that.
} else {
visited = true;
if (type.visit(this, thatType)) {
return true;
}
}
}
// Special case the "null|undefined" union and allow it to be cast
// to any cast to any type containing allowing either null|undefined.
if (!visited) {
JSType NULL_TYPE = thisType.getNativeType(JSTypeNative.NULL_TYPE);
JSType VOID_TYPE = thisType.getNativeType(JSTypeNative.VOID_TYPE);
return NULL_TYPE.visit(this, thatType) || VOID_TYPE.visit(this, thatType);
}
return false;
}
@Override
public Boolean caseTemplatizedType(
TemplatizedType thisType,
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>/*
* Copyright 2012 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
/**
* Checks for common errors, such as misplaced semicolons:
* <pre>
* if (x); act_now();
* </pre>
* or comparison against NaN:
* <pre>
* if (x === NaN) act();
* </pre>
* and generates warnings.
*
* @author johnlenz@google.com (John Lenz)
*/
final class CheckSuspiciousCode extends AbstractPostOrderCallback {
static final DiagnosticType SUSPICIOUS_SEMICOLON = DiagnosticType.warning(
"JSC_SUSPICIOUS_SEMICOLON",
"If this if/for/while really shouldn't have a body, use {}");
static final DiagnosticType SUSPICIOUS_COMPARISON_WITH_NAN =
DiagnosticType.warning(
"JSC_SUSPICIOUS_NAN",
"Comparison again NaN is always false. Did you mean isNaN()?");
CheckSuspiciousCode() {
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
checkMissingSemicolon(t, n);
checkNaN(t, n);
}
private void checkMissingSemicolon(NodeTraversal t, Node n) {
switch (n.getType()) {
case Token.IF:
Node trueCase = n.getFirstChild().getNext();
reportIfWasEmpty(t, trueCase);
Node elseCase = trueCase.getNext();
if (elseCase != null) {
reportIfWasEmpty(t, elseCase);
}
break;
case Token.WHILE:
case Token.FOR:
reportIfWasEmpty(t, NodeUtil.getLoopCodeBlock(n));
break;
}
}
private void reportIfWasEmpty(NodeTraversal t, Node block) {
Preconditions.checkState(block.isBlock());
// A semicolon is distinguished from a block without children by
// annotating it with EMPTY_BLOCK. Blocks without children are
// usually intentional, especially with loops.
if (!block.hasChildren() && block.wasEmptyNode()) {
t.getCompiler().report(
t.makeError(block, SUSPICIOUS_SEMICOLON));
}
}
private void checkNaN(NodeTraversal t
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>, Node n) {
switch (n.getType()) {
case Token.EQ:
case Token.GE:
case Token.GT:
case Token.LE:
case Token.LT:
case Token.NE:
case Token.SHEQ:
case Token.SHNE:
reportIfNaN(t, n.getFirstChild());
reportIfNaN(t, n.getLastChild());
}
}
private void reportIfNaN(NodeTraversal t, Node n) {
if (NodeUtil.isNaN(n)) {
t.getCompiler().report(
t.makeError(n.getParent(), SUSPICIOUS_COMPARISON_WITH_NAN));
}
}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>, int lineno, int charno,
CheckLevel level, DiagnosticType type, String... arguments) {
return new JSError(
sourceName, null, lineno, charno, type, level, arguments);
}
/**
* Creates a JSError from a file and Node position.
*
* @param sourceName The source file name
* @param n Determines the line and char position within the source file name
* @param type The DiagnosticType
* @param arguments Arguments to be incorporated into the message
*/
public static JSError make(String sourceName, Node n,
DiagnosticType type, String... arguments) {
return new JSError(sourceName, n, type, arguments);
}
/**
* Creates a JSError from a file and Node position.
*
* @param n Determines the line and char position and source file name
* @param type The DiagnosticType
* @param arguments Arguments to be incorporated into the message
*/
public static JSError make(Node n, DiagnosticType type, String... arguments) {
return new JSError(n.getSourceFileName(), n, type, arguments);
}
/**
* Creates a JSError from a file and Node position.
*
* @param sourceName The source file name
* @param n Determines the line and char position within the source file name
* @param type The DiagnosticType
* @param arguments Arguments to be incorporated into the message
*/
public static JSError make(String sourceName, Node n, CheckLevel level,
DiagnosticType type, String... arguments) {
return new JSError(sourceName, n, n.getLineno(), n.getCharno(), type, level,
arguments);
}
//
// JSError constructors
//
/**
* Creates a JSError at a CheckLevel for a source file location.
* Private to avoid any entanglement with code outside of the compiler.
*/
private JSError(
String sourceName, @Nullable Node node, int lineno, int charno,
DiagnosticType type, CheckLevel level, String... arguments) {
this.type = type;
this.node = node;
this.description = type.format.format(arguments);
this.lineNumber = lineno;
this.charno = charno;
this.sourceName = sourceName;
this.defaultLevel = level == null ? type.level : level;
this.level = level == null ? type.level : level;
}
/**
* Creates a JSError for a source file location. Private to avoid
* any entanglement with code outside of the compiler.
*/
private JSError(String sourceName, @Nullable Node node,
DiagnosticType type, String... arguments) {
this(sourceName,
node,
(node != null) ? node.getLineno() : -1,
(node != null) ? node.getCharno() : -1,
type, null, arguments);
}
public DiagnosticType getType() {
return type;
}
/**
* Format a message at the given level.
*
* @return the formatted message or {@code null}
*/
public String format(CheckLevel level, MessageFormatter formatter) {
switch (level) {
case ERROR:
return formatter.formatError(this
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>Convention convention;
final JSTypeRegistry typeRegistry;
private ChainableReverseAbstractInterpreter firstLink;
private ChainableReverseAbstractInterpreter nextLink;
/**
* Constructs an interpreter, which is the only link in a chain. Interpreters
* can be appended using {@link #append}.
*/
public ChainableReverseAbstractInterpreter(CodingConvention convention,
JSTypeRegistry typeRegistry) {
Preconditions.checkNotNull(convention);
this.convention = convention;
this.typeRegistry = typeRegistry;
firstLink = this;
nextLink = null;
}
/**
* Appends a link to {@code this}, returning the updated last link.
* <p>
* The pattern {@code new X().append(new Y())...append(new Z())} forms a
* chain starting with X, then Y, then ... Z.
* @param lastLink a chainable interpreter, with no next link
* @return the updated last link
*/
public ChainableReverseAbstractInterpreter append(
ChainableReverseAbstractInterpreter lastLink) {
Preconditions.checkArgument(lastLink.nextLink == null);
this.nextLink = lastLink;
lastLink.firstLink = this.firstLink;
return lastLink;
}
/**
* Gets the first link of this chain.
*/
public ChainableReverseAbstractInterpreter getFirst() {
return firstLink;
}
/**
* Calculates the preciser scope starting with the first link.
*/
protected FlowScope firstPreciserScopeKnowingConditionOutcome(Node condition,
FlowScope blindScope, boolean outcome) {
return firstLink.getPreciserScopeKnowingConditionOutcome(
condition, blindScope, outcome);
}
/**
* Delegates the calculation of the preciser scope to the next link.
* If there is no next link, returns the blind scope.
*/
protected FlowScope nextPreciserScopeKnowingConditionOutcome(Node condition,
FlowScope blindScope, boolean outcome) {
return nextLink != null ? nextLink.getPreciserScopeKnowingConditionOutcome(
condition, blindScope, outcome) : blindScope;
}
/**
* Returns the type of a node in the given scope if the node corresponds to a
* name whose type is capable of being refined.
* @return The current type of the node if it can be refined, null otherwise.
*/
protected JSType getTypeIfRefinable(Node node, FlowScope scope) {
switch (node.getType()) {
case Token.NAME:
StaticSlot<JSType> nameVar = scope.getSlot(node.getString());
if (nameVar != null) {
JSType nameVarType = nameVar.getType();
if (nameVarType == null) {
nameVarType = node.getJSType();
}
return nameVarType;
}
return null;
case Token.GETPROP:
String qualifiedName = node.getQualifiedName();
if (qualifiedName == null) {
return null;
}
StaticSlot<JSType> propVar = scope.getSlot(qualifiedName);
JSType propVarType = null;
if (propVar != null) {
propVarType = propVar.getType();
}
if (propVarType == null) {
propVarType = node.get
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>JSType();
}
if (propVarType == null) {
propVarType = getNativeType(UNKNOWN_TYPE);
}
return propVarType;
}
return null;
}
/**
* Declares a refined type in {@code scope} for the name represented by
* {@code node}. It must be possible to refine the type of the given node in
* the given scope, as determined by {@link #getTypeIfRefinable}.
*/
protected void declareNameInScope(FlowScope scope, Node node, JSType type) {
switch (node.getType()) {
case Token.NAME:
scope.inferSlotType(node.getString(), type);
break;
case Token.GETPROP:
String qualifiedName = node.getQualifiedName();
Preconditions.checkNotNull(qualifiedName);
JSType origType = node.getJSType();
origType = origType == null ? getNativeType(UNKNOWN_TYPE) : origType;
scope.inferQualifiedSlot(node, qualifiedName, origType, type);
break;
case Token.THIS:
// "this" references aren't currently modeled in the CFG.
break;
default:
throw new IllegalArgumentException("Node cannot be refined. \n" +
node.toStringTree());
}
}
/**
* @see #getRestrictedWithoutUndefined(JSType)
*/
private final Visitor<JSType> restrictUndefinedVisitor =
new Visitor<JSType>() {
@Override
public JSType caseEnumElementType(EnumElementType enumElementType) {
JSType type = enumElementType.getPrimitiveType().visit(this);
if (type != null && enumElementType.getPrimitiveType().isEquivalentTo(type)) {
return enumElementType;
} else {
return type;
}
}
@Override
public JSType caseAllType() {
return typeRegistry.createUnionType(OBJECT_TYPE, NUMBER_TYPE,
STRING_TYPE, BOOLEAN_TYPE, NULL_TYPE);
}
@Override
public JSType caseNoObjectType() {
return getNativeType(NO_OBJECT_TYPE);
}
@Override
public JSType caseNoType() {
return getNativeType(NO_TYPE);
}
@Override
public JSType caseBooleanType() {
return getNativeType(BOOLEAN_TYPE);
}
@Override
public JSType caseFunctionType(FunctionType type) {
return type;
}
@Override
public JSType caseNullType() {
return getNativeType(NULL_TYPE);
}
@Override
public JSType caseNumberType() {
return getNativeType(NUMBER_TYPE);
}
@Override
public JSType caseObjectType(ObjectType type) {
return type;
}
@Override
public JSType caseStringType() {
return getNativeType(STRING_TYPE);
}
@Override
public JSType caseUnionType(UnionType type) {
return type.getRestrictedUnion(getNativeType(VOID_TYPE));
}
@Override
public JSType caseUnknownType() {
return getNativeType(UNKNOWN_TYPE);
}
@Override
public JSType caseVoidType() {
return null;
}
@Override
public JSType caseTemplatizedType(TemplatizedType type) {
return caseObjectType(
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>/*
* Copyright 2010 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.collect.Maps;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.Map;
/**
* Filters warnings based on in-code {@code @suppress} annotations.
* @author nicksantos@google.com (Nick Santos)
*/
class SuppressDocWarningsGuard extends WarningsGuard {
private static final long serialVersionUID = 1L;
/** Warnings guards for each suppressible warnings group, indexed by name. */
private final Map<String, DiagnosticGroupWarningsGuard> suppressors =
Maps.newHashMap();
/**
* The suppressible groups, indexed by name.
*/
SuppressDocWarningsGuard(Map<String, DiagnosticGroup> suppressibleGroups) {
for (Map.Entry<String, DiagnosticGroup> entry :
suppressibleGroups.entrySet()) {
suppressors.put(
entry.getKey(),
new DiagnosticGroupWarningsGuard(
entry.getValue(),
CheckLevel.OFF));
}
}
@Override
public CheckLevel level(JSError error) {
Node node = error.node;
if (node != null) {
boolean visitedFunction = false;
for (Node current = node;
current != null;
current = current.getParent()) {
int type = current.getType();
JSDocInfo info = null;
if (type == Token.FUNCTION) {
info = NodeUtil.getBestJSDocInfo(current);
visitedFunction = true;
} else if (type == Token.SCRIPT) {
info = current.getJSDocInfo();
} else if (current.isVar() || current.isAssign()) {
// There's one edge case we're worried about:
// if the warning points to an assigment to a function, we
// want the suppressions on that function to apply.
// It's OK if we double-count some cases.
Node rhs = NodeUtil.getRValueOfLValue(current.getFirstChild());
if (rhs != null) {
if (rhs.isCast()) {
rhs = rhs.getFirstChild();
}
if (rhs.isFunction() && !visitedFunction) {
info = NodeUtil.getBestJSDocInfo(current);
}
}
}
if (info != null) {
for (String suppressor : info.getSuppressions()) {
WarningsGuard guard = suppressors.get(suppressor);
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>SwitchStatement statementNode);
abstract T processThrowStatement(ThrowStatement statementNode);
abstract T processTryStatement(TryStatement statementNode);
abstract T processUnaryExpression(UnaryExpression exprNode);
abstract T processVariableDeclaration(VariableDeclaration declarationNode);
abstract T processVariableInitializer(VariableInitializer initializerNode);
abstract T processWhileLoop(WhileLoop loopNode);
abstract T processWithStatement(WithStatement statementNode);
abstract T processIllegalToken(AstNode node);
public T process(AstNode node) {
switch (node.getType()) {
case Token.ADD:
case Token.AND:
case Token.BITAND:
case Token.BITOR:
case Token.BITXOR:
case Token.COMMA:
case Token.DIV:
case Token.EQ:
case Token.GE:
case Token.GT:
case Token.IN:
case Token.INSTANCEOF:
case Token.LE:
case Token.LSH:
case Token.LT:
case Token.MOD:
case Token.MUL:
case Token.NE:
case Token.OR:
case Token.RSH:
case Token.SHEQ:
case Token.SHNE:
case Token.SUB:
case Token.URSH:
return processInfixExpression((InfixExpression) node);
case Token.ARRAYLIT:
return processArrayLiteral((ArrayLiteral) node);
case Token.ASSIGN:
case Token.ASSIGN_ADD:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_DIV:
case Token.ASSIGN_LSH:
case Token.ASSIGN_MOD:
case Token.ASSIGN_MUL:
case Token.ASSIGN_RSH:
case Token.ASSIGN_SUB:
case Token.ASSIGN_URSH:
return processAssignment((Assignment) node);
case Token.BITNOT:
case Token.DEC:
case Token.DELPROP:
case Token.INC:
case Token.NEG:
case Token.NOT:
case Token.POS:
case Token.TYPEOF:
case Token.VOID:
return processUnaryExpression((UnaryExpression) node);
case Token.BLOCK:
if (node instanceof Block) {
return processBlock((Block) node);
} else if (node instanceof Scope) {
return processScope((Scope) node);
} else {
throw new IllegalStateException("Unexpected node type. class: " +
node.getClass() +
" type: " +
Token.typeToName(node.getType()));
}
case Token.BREAK:
return processBreakStatement((BreakStatement) node);
case Token.CALL:
return processFunctionCall((FunctionCall) node);
case Token.CASE:
case Token.DEFAULT:
return processSwitchCase((SwitchCase) node);
case Token.CATCH:
return processCatchClause((CatchClause) node);
case Token.COLON:
return processObjectProperty((ObjectProperty) node);
case Token.CONTINUE:
return processContinueStatement((ContinueStatement) node);
case Token.DO:
return processDoLoop((DoLoop) node);
case Token.EMPTY:
return (node instanceof EmptyExpression) ?
processEmptyExpression((EmptyExpression) node) :
processEmptyStatement((EmptyStatement)
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> node);
case Token.EXPR_RESULT:
case Token.EXPR_VOID:
if (node instanceof ExpressionStatement) {
return processExpressionStatement((ExpressionStatement) node);
} else if (node instanceof LabeledStatement) {
return processLabeledStatement((LabeledStatement) node);
} else {
throw new IllegalStateException("Unexpected node type. class: " +
node.getClass() +
" type: " +
Token.typeToName(node.getType()));
}
case Token.DEBUGGER:
case Token.FALSE:
case Token.NULL:
case Token.THIS:
case Token.TRUE:
return processKeywordLiteral((KeywordLiteral) node);
case Token.FOR:
if (node instanceof ForInLoop) {
return processForInLoop((ForInLoop) node);
} else if (node instanceof ForLoop) {
return processForLoop((ForLoop) node);
} else {
throw new IllegalStateException("Unexpected node type. class: " +
node.getClass() +
" type: " +
Token.typeToName(node.getType()));
}
case Token.FUNCTION:
return processFunctionNode((FunctionNode) node);
case Token.GETELEM:
return processElementGet((ElementGet) node);
case Token.GETPROP:
return processPropertyGet((PropertyGet) node);
case Token.HOOK:
return processConditionalExpression((ConditionalExpression) node);
case Token.IF:
return processIfStatement((IfStatement) node);
case Token.LABEL:
return processLabel((Label) node);
case Token.LP:
return processParenthesizedExpression((ParenthesizedExpression) node);
case Token.NAME:
return processName((Name) node);
case Token.NEW:
return processNewExpression((NewExpression) node);
case Token.NUMBER:
return processNumberLiteral((NumberLiteral) node);
case Token.OBJECTLIT:
return processObjectLiteral((ObjectLiteral) node);
case Token.REGEXP:
return processRegExpLiteral((RegExpLiteral) node);
case Token.RETURN:
return processReturnStatement((ReturnStatement) node);
case Token.SCRIPT:
return processAstRoot((AstRoot) node);
case Token.STRING:
return processStringLiteral((StringLiteral) node);
case Token.SWITCH:
return processSwitchStatement((SwitchStatement) node);
case Token.THROW:
return processThrowStatement((ThrowStatement) node);
case Token.TRY:
return processTryStatement((TryStatement) node);
case Token.CONST:
case Token.VAR:
if (node instanceof VariableDeclaration) {
return processVariableDeclaration((VariableDeclaration) node);
} else if (node instanceof VariableInitializer) {
return processVariableInitializer((VariableInitializer) node);
} else {
throw new IllegalStateException("Unexpected node type. class: " +
node.getClass() +
" type: " +
Token.typeToName(node.getType()));
}
case Token.WHILE:
return processWhileLoop((WhileLoop) node);
case Token.WITH:
return processWithStatement((WithStatement) node);
}
return processIllegalToken(node);
}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>/*
* Copyright 2004 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.InputId;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.TokenStream;
import com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.StaticSourceFile;
import com.google.javascript.rhino.jstype.TernaryValue;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
/**
* NodeUtil contains generally useful AST utilities.
*
*/
public final class NodeUtil {
static final long MAX_POSITIVE_INTEGER_NUMBER = (long) Math.pow(2, 53);
static final String JSC_PROPERTY_NAME_FN = "JSCompiler_renameProperty";
static final char LARGEST_BASIC_LATIN = 0x7f;
/** the set of builtin constructors that don't have side effects. */
private static final Set<String> CONSTRUCTORS_WITHOUT_SIDE_EFFECTS =
new HashSet<String>(Arrays.asList(
"Array",
"Date",
"Error",
"Object",
"RegExp",
"XMLHttpRequest"));
// Utility class; do not instantiate.
private NodeUtil() {}
/**
* Gets the boolean value of a node that represents a expression. This method
* effectively emulates the <code>Boolean()</code> JavaScript cast function.
* Note: unlike getBooleanValue this function does not return UNKNOWN
* for expressions with side-effects.
*/
static TernaryValue getImpureBooleanValue(Node n) {
switch (n.getType()) {
case Token.ASSIGN:
case Token.COMMA:
// For ASSIGN and COMMA the value is the value of the RHS.
return get
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>ImpureBooleanValue(n.getLastChild());
case Token.NOT:
TernaryValue value = getImpureBooleanValue(n.getLastChild());
return value.not();
case Token.AND: {
TernaryValue lhs = getImpureBooleanValue(n.getFirstChild());
TernaryValue rhs = getImpureBooleanValue(n.getLastChild());
return lhs.and(rhs);
}
case Token.OR: {
TernaryValue lhs = getImpureBooleanValue(n.getFirstChild());
TernaryValue rhs = getImpureBooleanValue(n.getLastChild());
return lhs.or(rhs);
}
case Token.HOOK: {
TernaryValue trueValue = getImpureBooleanValue(
n.getFirstChild().getNext());
TernaryValue falseValue = getImpureBooleanValue(n.getLastChild());
if (trueValue.equals(falseValue)) {
return trueValue;
} else {
return TernaryValue.UNKNOWN;
}
}
case Token.ARRAYLIT:
case Token.OBJECTLIT:
// ignoring side-effects
return TernaryValue.TRUE;
case Token.VOID:
return TernaryValue.FALSE;
default:
return getPureBooleanValue(n);
}
}
/**
* Gets the boolean value of a node that represents a literal. This method
* effectively emulates the <code>Boolean()</code> JavaScript cast function
* except it return UNKNOWN for known values with side-effects, use
* getImpureBooleanValue if you don't care about side-effects.
*/
static TernaryValue getPureBooleanValue(Node n) {
switch (n.getType()) {
case Token.STRING:
return TernaryValue.forBoolean(n.getString().length() > 0);
case Token.NUMBER:
return TernaryValue.forBoolean(n.getDouble() != 0);
case Token.NOT:
return getPureBooleanValue(n.getLastChild()).not();
case Token.NULL:
case Token.FALSE:
return TernaryValue.FALSE;
case Token.VOID:
if (!mayHaveSideEffects(n.getFirstChild())) {
return TernaryValue.FALSE;
}
break;
case Token.NAME:
String name = n.getString();
if ("undefined".equals(name)
|| "NaN".equals(name)) {
// We assume here that programs don't change the value of the keyword
// undefined to something other than the value undefined.
return TernaryValue.FALSE;
} else if ("Infinity".equals(name)) {
return TernaryValue.TRUE;
}
break;
case Token.TRUE:
case Token.REGEXP:
return TernaryValue.TRUE;
case Token.ARRAYLIT:
case Token.OBJECTLIT:
if (!mayHaveSideEffects(n)) {
return TernaryValue.TRUE;
}
break;
}
return TernaryValue.UNKNOWN;
}
/**
* Gets the value of a node as a String, or null if it cannot be converted.
* When it returns a non-null String, this method effectively emulates the
* <code>String()</code> JavaScript cast function.
*/
static String getStringValue(Node n) {
// TODO(user
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>): regex literals as well.
switch (n.getType()) {
case Token.STRING:
case Token.STRING_KEY:
return n.getString();
case Token.NAME:
String name = n.getString();
if ("undefined".equals(name)
|| "Infinity".equals(name)
|| "NaN".equals(name)) {
return name;
}
break;
case Token.NUMBER:
return getStringValue(n.getDouble());
case Token.FALSE:
return "false";
case Token.TRUE:
return "true";
case Token.NULL:
return "null";
case Token.VOID:
return "undefined";
case Token.NOT:
TernaryValue child = getPureBooleanValue(n.getFirstChild());
if (child != TernaryValue.UNKNOWN) {
return child.toBoolean(true) ? "false" : "true"; // reversed.
}
break;
case Token.ARRAYLIT:
return arrayToString(n);
case Token.OBJECTLIT:
return "[object Object]";
}
return null;
}
static String getStringValue(double value) {
long longValue = (long) value;
// Return "1" instead of "1.0"
if (longValue == value) {
return Long.toString(longValue);
} else {
return Double.toString(value);
}
}
/**
* When converting arrays to string using Array.prototype.toString or
* Array.prototype.join, the rules for conversion to String are different
* than converting each element individually. Specifically, "null" and
* "undefined" are converted to an empty string.
* @param n A node that is a member of an Array.
* @return The string representation.
*/
static String getArrayElementStringValue(Node n) {
return (NodeUtil.isNullOrUndefined(n) || n.isEmpty())
? "" : getStringValue(n);
}
static String arrayToString(Node literal) {
Node first = literal.getFirstChild();
StringBuilder result = new StringBuilder();
for (Node n = first; n != null; n = n.getNext()) {
String childValue = getArrayElementStringValue(n);
if (childValue == null) {
return null;
}
if (n != first) {
result.append(',');
}
result.append(childValue);
}
return result.toString();
}
/**
* Gets the value of a node as a Number, or null if it cannot be converted.
* When it returns a non-null Double, this method effectively emulates the
* <code>Number()</code> JavaScript cast function.
*/
static Double getNumberValue(Node n) {
switch (n.getType()) {
case Token.TRUE:
return 1.0;
case Token.FALSE:
case Token.NULL:
return 0.0;
case Token.NUMBER:
return n.getDouble();
case Token.VOID:
if (mayHaveSideEffects(n.getFirstChild())) {
return null;
} else {
return Double.NaN;
}
case Token.NAME:
// Check for known constants
String name = n.getString();
if (name.equals("undefined")) {
return Double.NaN;
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> }
if (name.equals("NaN")) {
return Double.NaN;
}
if (name.equals("Infinity")) {
return Double.POSITIVE_INFINITY;
}
return null;
case Token.NEG:
if (n.getChildCount() == 1 && n.getFirstChild().isName()
&& n.getFirstChild().getString().equals("Infinity")) {
return Double.NEGATIVE_INFINITY;
}
return null;
case Token.NOT:
TernaryValue child = getPureBooleanValue(n.getFirstChild());
if (child != TernaryValue.UNKNOWN) {
return child.toBoolean(true) ? 0.0 : 1.0; // reversed.
}
break;
case Token.STRING:
return getStringNumberValue(n.getString());
case Token.ARRAYLIT:
case Token.OBJECTLIT:
String value = getStringValue(n);
return value != null ? getStringNumberValue(value) : null;
}
return null;
}
static Double getStringNumberValue(String rawJsString) {
if (rawJsString.contains("\u000b")) {
// vertical tab is not always whitespace
return null;
}
String s = trimJsWhiteSpace(rawJsString);
// return ScriptRuntime.toNumber(s);
if (s.length() == 0) {
return 0.0;
}
if (s.length() > 2
&& s.charAt(0) == '0'
&& (s.charAt(1) == 'x' || s.charAt(1) == 'X')) {
// Attempt to convert hex numbers.
try {
return Double.valueOf(Integer.parseInt(s.substring(2), 16));
} catch (NumberFormatException e) {
return Double.NaN;
}
}
if (s.length() > 3
&& (s.charAt(0) == '-' || s.charAt(0) == '+')
&& s.charAt(1) == '0'
&& (s.charAt(2) == 'x' || s.charAt(2) == 'X')) {
// hex numbers with explicit signs vary between browsers.
return null;
}
// Firefox and IE treat the "Infinity" differently. Firefox is case
// insensitive, but IE treats "infinity" as NaN. So leave it alone.
if (s.equals("infinity")
|| s.equals("-infinity")
|| s.equals("+infinity")) {
return null;
}
try {
return Double.parseDouble(s);
} catch (NumberFormatException e) {
return Double.NaN;
}
}
static String trimJsWhiteSpace(String s) {
int start = 0;
int end = s.length();
while (end > 0
&& isStrWhiteSpaceChar(s.charAt(end - 1)) == TernaryValue.TRUE) {
end--;
}
while (start < end
&& isStrWhiteSpaceChar(s.charAt(start)) == TernaryValue.TRUE) {
start++;
}
return s.substring(start, end);
}
/**
* Copied from Rhino's ScriptRuntime
*/
public static TernaryValue isStrWhiteSpaceChar(int c)
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> {
switch (c) {
case '\u000B': // <VT>
return TernaryValue.UNKNOWN; // IE says "no", ECMAScript says "yes"
case ' ': // <SP>
case '\n': // <LF>
case '\r': // <CR>
case '\t': // <TAB>
case '\u00A0': // <NBSP>
case '\u000C': // <FF>
case '\u2028': // <LS>
case '\u2029': // <PS>
case '\uFEFF': // <BOM>
return TernaryValue.TRUE;
default:
return (Character.getType(c) == Character.SPACE_SEPARATOR)
? TernaryValue.TRUE : TernaryValue.FALSE;
}
}
/**
* Gets the function's name. This method recognizes five forms:
* <ul>
* <li>{@code function name() ...}</li>
* <li>{@code var name = function() ...}</li>
* <li>{@code qualified.name = function() ...}</li>
* <li>{@code var name2 = function name1() ...}</li>
* <li>{@code qualified.name2 = function name1() ...}</li>
* </ul>
* In two last cases with named function expressions, the second name is
* returned (the variable of qualified name).
*
* @param n a node whose type is {@link Token#FUNCTION}
* @return the function's name, or {@code null} if it has no name
*/
static String getFunctionName(Node n) {
Preconditions.checkState(n.isFunction());
Node parent = n.getParent();
switch (parent.getType()) {
case Token.NAME:
// var name = function() ...
// var name2 = function name1() ...
return parent.getQualifiedName();
case Token.ASSIGN:
// qualified.name = function() ...
// qualified.name2 = function name1() ...
return parent.getFirstChild().getQualifiedName();
default:
// function name() ...
String name = n.getFirstChild().getQualifiedName();
return name;
}
}
/**
* Gets the function's name. This method recognizes the forms:
* <ul>
* <li>{@code {'name': function() ...}}</li>
* <li>{@code {name: function() ...}}</li>
* <li>{@code function name() ...}</li>
* <li>{@code var name = function() ...}</li>
* <li>{@code qualified.name = function() ...}</li>
* <li>{@code var name2 = function name1() ...}</li>
* <li>{@code qualified.name2 = function name1() ...}</li>
* </ul>
*
* @param n a node whose type is {@link Token#FUNCTION}
* @return the function's name, or {@code null} if it has no name
*/
public static String getNearestFunctionName(Node n) {
if (!n.isFunction()) {
return null;
}
String name = getFunctionName(n
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>);
if (name != null) {
return name;
}
// Check for the form { 'x' : function() { } }
Node parent = n.getParent();
switch (parent.getType()) {
case Token.SETTER_DEF:
case Token.GETTER_DEF:
case Token.STRING_KEY:
// Return the name of the literal's key.
return parent.getString();
case Token.NUMBER:
return getStringValue(parent);
}
return null;
}
/**
* Returns true if this is an immutable value.
*/
static boolean isImmutableValue(Node n) {
switch (n.getType()) {
case Token.STRING:
case Token.NUMBER:
case Token.NULL:
case Token.TRUE:
case Token.FALSE:
return true;
case Token.CAST:
case Token.NOT:
return isImmutableValue(n.getFirstChild());
case Token.VOID:
case Token.NEG:
return isImmutableValue(n.getFirstChild());
case Token.NAME:
String name = n.getString();
// We assume here that programs don't change the value of the keyword
// undefined to something other than the value undefined.
return "undefined".equals(name)
|| "Infinity".equals(name)
|| "NaN".equals(name);
}
return false;
}
/**
* Returns true if the operator on this node is symmetric
*/
static boolean isSymmetricOperation(Node n) {
switch (n.getType()) {
case Token.EQ: // equal
case Token.NE: // not equal
case Token.SHEQ: // exactly equal
case Token.SHNE: // exactly not equal
case Token.MUL: // multiply, unlike add it only works on numbers
// or results NaN if any of the operators is not a number
return true;
}
return false;
}
/**
* Returns true if the operator on this node is relational.
* the returned set does not include the equalities.
*/
static boolean isRelationalOperation(Node n) {
switch (n.getType()) {
case Token.GT: // equal
case Token.GE: // not equal
case Token.LT: // exactly equal
case Token.LE: // exactly not equal
return true;
}
return false;
}
/**
* Returns the inverse of an operator if it is invertible.
* ex. '>' ==> '<'
*/
static int getInverseOperator(int type) {
switch (type) {
case Token.GT:
return Token.LT;
case Token.LT:
return Token.GT;
case Token.GE:
return Token.LE;
case Token.LE:
return Token.GE;
}
return Token.ERROR;
}
/**
* Returns true if this is a literal value. We define a literal value
* as any node that evaluates to the same thing regardless of when or
* where it is evaluated. So /xyz/ and [3, 5] are literals, but
* the name a is not.
*
* Function literals do not meet this definition, because they
* lexically capture variables. For example, if you have
* <code>
* function() { return a; }
* </code
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>>
* If it is evaluated in a different scope, then it
* captures a different variable. Even if the function did not read
* any captured variables directly, it would still fail this definition,
* because it affects the lifecycle of variables in the enclosing scope.
*
* However, a function literal with respect to a particular scope is
* a literal.
*
* @param includeFunctions If true, all function expressions will be
* treated as literals.
*/
static boolean isLiteralValue(Node n, boolean includeFunctions) {
switch (n.getType()) {
case Token.CAST:
return isLiteralValue(n.getFirstChild(), includeFunctions);
case Token.ARRAYLIT:
for (Node child = n.getFirstChild(); child != null;
child = child.getNext()) {
if ((!child.isEmpty()) && !isLiteralValue(child, includeFunctions)) {
return false;
}
}
return true;
case Token.REGEXP:
// Return true only if all children are const.
for (Node child = n.getFirstChild(); child != null;
child = child.getNext()) {
if (!isLiteralValue(child, includeFunctions)) {
return false;
}
}
return true;
case Token.OBJECTLIT:
// Return true only if all values are const.
for (Node child = n.getFirstChild(); child != null;
child = child.getNext()) {
if (!isLiteralValue(child.getFirstChild(), includeFunctions)) {
return false;
}
}
return true;
case Token.FUNCTION:
return includeFunctions && !NodeUtil.isFunctionDeclaration(n);
default:
return isImmutableValue(n);
}
}
/**
* Determines whether the given value may be assigned to a define.
*
* @param val The value being assigned.
* @param defines The list of names of existing defines.
*/
static boolean isValidDefineValue(Node val, Set<String> defines) {
switch (val.getType()) {
case Token.STRING:
case Token.NUMBER:
case Token.TRUE:
case Token.FALSE:
return true;
// Binary operators are only valid if both children are valid.
case Token.ADD:
case Token.BITAND:
case Token.BITNOT:
case Token.BITOR:
case Token.BITXOR:
case Token.DIV:
case Token.EQ:
case Token.GE:
case Token.GT:
case Token.LE:
case Token.LSH:
case Token.LT:
case Token.MOD:
case Token.MUL:
case Token.NE:
case Token.RSH:
case Token.SHEQ:
case Token.SHNE:
case Token.SUB:
case Token.URSH:
return isValidDefineValue(val.getFirstChild(), defines)
&& isValidDefineValue(val.getLastChild(), defines);
// Unary operators are valid if the child is valid.
case Token.NOT:
case Token.NEG:
case Token.POS:
return isValidDefineValue(val.getFirstChild(), defines);
// Names are valid if and only if they are defines themselves.
case Token.NAME:
case Token.GETPROP:
if (val.isQualifiedName()) {
return defines
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>.contains(val.getQualifiedName());
}
}
return false;
}
/**
* Returns whether this a BLOCK node with no children.
*
* @param block The node.
*/
static boolean isEmptyBlock(Node block) {
if (!block.isBlock()) {
return false;
}
for (Node n = block.getFirstChild(); n != null; n = n.getNext()) {
if (!n.isEmpty()) {
return false;
}
}
return true;
}
static boolean isSimpleOperator(Node n) {
return isSimpleOperatorType(n.getType());
}
/**
* A "simple" operator is one whose children are expressions,
* has no direct side-effects (unlike '+='), and has no
* conditional aspects (unlike '||').
*/
static boolean isSimpleOperatorType(int type) {
switch (type) {
case Token.ADD:
case Token.BITAND:
case Token.BITNOT:
case Token.BITOR:
case Token.BITXOR:
case Token.COMMA:
case Token.DIV:
case Token.EQ:
case Token.GE:
case Token.GETELEM:
case Token.GETPROP:
case Token.GT:
case Token.INSTANCEOF:
case Token.LE:
case Token.LSH:
case Token.LT:
case Token.MOD:
case Token.MUL:
case Token.NE:
case Token.NOT:
case Token.RSH:
case Token.SHEQ:
case Token.SHNE:
case Token.SUB:
case Token.TYPEOF:
case Token.VOID:
case Token.POS:
case Token.NEG:
case Token.URSH:
return true;
default:
return false;
}
}
/**
* Creates an EXPR_RESULT.
*
* @param child The expression itself.
* @return Newly created EXPR node with the child as subexpression.
*/
static Node newExpr(Node child) {
return IR.exprResult(child).srcref(child);
}
/**
* Returns true if the node may create new mutable state, or change existing
* state.
*
* @see <a href="http://www.xkcd.org/326/">XKCD Cartoon</a>
*/
static boolean mayEffectMutableState(Node n) {
return mayEffectMutableState(n, null);
}
static boolean mayEffectMutableState(Node n, AbstractCompiler compiler) {
return checkForStateChangeHelper(n, true, compiler);
}
/**
* Returns true if the node which may have side effects when executed.
*/
static boolean mayHaveSideEffects(Node n) {
return mayHaveSideEffects(n, null);
}
static boolean mayHaveSideEffects(Node n, AbstractCompiler compiler) {
return checkForStateChangeHelper(n, false, compiler);
}
/**
* Returns true if some node in n's subtree changes application state.
* If {@code checkForNewObjects} is true, we assume that newly created
* mutable objects (like object literals) change state. Otherwise, we assume
* that they have no side effects.
*/
private static boolean checkForStateChange
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>Helper(
Node n, boolean checkForNewObjects, AbstractCompiler compiler) {
// Rather than id which ops may have side effects, id the ones
// that we know to be safe
switch (n.getType()) {
// other side-effect free statements and expressions
case Token.CAST:
case Token.AND:
case Token.BLOCK:
case Token.EXPR_RESULT:
case Token.HOOK:
case Token.IF:
case Token.IN:
case Token.PARAM_LIST:
case Token.NUMBER:
case Token.OR:
case Token.THIS:
case Token.TRUE:
case Token.FALSE:
case Token.NULL:
case Token.STRING:
case Token.STRING_KEY:
case Token.SWITCH:
case Token.TRY:
case Token.EMPTY:
break;
// Throws are by definition side effects
case Token.THROW:
return true;
case Token.OBJECTLIT:
if (checkForNewObjects) {
return true;
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (checkForStateChangeHelper(
c.getFirstChild(), checkForNewObjects, compiler)) {
return true;
}
}
return false;
case Token.ARRAYLIT:
case Token.REGEXP:
if (checkForNewObjects) {
return true;
}
break;
case Token.VAR: // empty var statement (no declaration)
case Token.NAME: // variable by itself
if (n.getFirstChild() != null) {
return true;
}
break;
case Token.FUNCTION:
// Function expressions don't have side-effects, but function
// declarations change the namespace. Either way, we don't need to
// check the children, since they aren't executed at declaration time.
return checkForNewObjects || !isFunctionExpression(n);
case Token.NEW:
if (checkForNewObjects) {
return true;
}
if (!constructorCallHasSideEffects(n)) {
// loop below will see if the constructor parameters have
// side-effects
break;
}
return true;
case Token.CALL:
// calls to functions that have no side effects have the no
// side effect property set.
if (!functionCallHasSideEffects(n, compiler)) {
// loop below will see if the function parameters have
// side-effects
break;
}
return true;
default:
if (isSimpleOperator(n)) {
break;
}
if (isAssignmentOp(n)) {
Node assignTarget = n.getFirstChild();
if (assignTarget.isName()) {
return true;
}
// Assignments will have side effects if
// a) The RHS has side effects, or
// b) The LHS has side effects, or
// c) A name on the LHS will exist beyond the life of this statement.
if (checkForStateChangeHelper(
n.getFirstChild(), checkForNewObjects, compiler) ||
checkForStateChangeHelper(
n.getLastChild(), checkForNewObjects, compiler)) {
return true;
}
if (isGet(assignTarget)) {
// If the object being assigned to is a local object, don't
// consider
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> this a side-effect as it can't be referenced
// elsewhere. Don't do this recursively as the property might
// be an alias of another object, unlike a literal below.
Node current = assignTarget.getFirstChild();
if (evaluatesToLocalValue(current)) {
return false;
}
// A literal value as defined by "isLiteralValue" is guaranteed
// not to be an alias, or any components which are aliases of
// other objects.
// If the root object is a literal don't consider this a
// side-effect.
while (isGet(current)) {
current = current.getFirstChild();
}
return !isLiteralValue(current, true);
} else {
// TODO(johnlenz): remove this code and make this an exception. This
// is here only for legacy reasons, the AST is not valid but
// preserve existing behavior.
return !isLiteralValue(assignTarget, true);
}
}
return true;
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (checkForStateChangeHelper(c, checkForNewObjects, compiler)) {
return true;
}
}
return false;
}
/**
* Do calls to this constructor have side effects?
*
* @param callNode - constructor call node
*/
static boolean constructorCallHasSideEffects(Node callNode) {
return constructorCallHasSideEffects(callNode, null);
}
static boolean constructorCallHasSideEffects(
Node callNode, AbstractCompiler compiler) {
if (!callNode.isNew()) {
throw new IllegalStateException(
"Expected NEW node, got " + Token.name(callNode.getType()));
}
if (callNode.isNoSideEffectsCall()) {
return false;
}
if (callNode.isOnlyModifiesArgumentsCall() &&
allArgsUnescapedLocal(callNode)) {
return false;
}
Node nameNode = callNode.getFirstChild();
if (nameNode.isName() &&
CONSTRUCTORS_WITHOUT_SIDE_EFFECTS.contains(nameNode.getString())) {
return false;
}
return true;
}
// A list of built-in object creation or primitive type cast functions that
// can also be called as constructors but lack side-effects.
// TODO(johnlenz): consider adding an extern annotation for this.
private static final Set<String> BUILTIN_FUNCTIONS_WITHOUT_SIDEEFFECTS =
ImmutableSet.of(
"Object", "Array", "String", "Number", "Boolean", "RegExp", "Error");
private static final Set<String> OBJECT_METHODS_WITHOUT_SIDEEFFECTS =
ImmutableSet.of("toString", "valueOf");
private static final Set<String> REGEXP_METHODS =
ImmutableSet.of("test", "exec");
private static final Set<String> STRING_REGEXP_METHODS =
ImmutableSet.of("match", "replace", "search", "split");
/**
* Returns true if calls to this function have side effects.
*
* @param callNode - function call node
*/
static boolean functionCallHasSideEffects(Node callNode) {
return functionCallHasSideEffects(callNode
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>, null);
}
/**
* Returns true if calls to this function have side effects.
*
* @param callNode The call node to inspected.
* @param compiler A compiler object to provide program state changing
* context information. Can be null.
*/
static boolean functionCallHasSideEffects(
Node callNode, @Nullable AbstractCompiler compiler) {
if (!callNode.isCall()) {
throw new IllegalStateException(
"Expected CALL node, got " + Token.name(callNode.getType()));
}
if (callNode.isNoSideEffectsCall()) {
return false;
}
if (callNode.isOnlyModifiesArgumentsCall() &&
allArgsUnescapedLocal(callNode)) {
return false;
}
Node nameNode = callNode.getFirstChild();
// Built-in functions with no side effects.
if (nameNode.isName()) {
String name = nameNode.getString();
if (BUILTIN_FUNCTIONS_WITHOUT_SIDEEFFECTS.contains(name)) {
return false;
}
} else if (nameNode.isGetProp()) {
if (callNode.hasOneChild()
&& OBJECT_METHODS_WITHOUT_SIDEEFFECTS.contains(
nameNode.getLastChild().getString())) {
return false;
}
if (callNode.isOnlyModifiesThisCall()
&& evaluatesToLocalValue(nameNode.getFirstChild())) {
return false;
}
// Math.floor has no side-effects.
// TODO(nicksantos): This is a terrible terrible hack, until
// I create a definitionProvider that understands namespacing.
if (nameNode.getFirstChild().isName()) {
if ("Math.floor".equals(nameNode.getQualifiedName())) {
return false;
}
}
if (compiler != null && !compiler.hasRegExpGlobalReferences()) {
if (nameNode.getFirstChild().isRegExp()
&& REGEXP_METHODS.contains(nameNode.getLastChild().getString())) {
return false;
} else if (nameNode.getFirstChild().isString()
&& STRING_REGEXP_METHODS.contains(
nameNode.getLastChild().getString())) {
Node param = nameNode.getNext();
if (param != null &&
(param.isString() || param.isRegExp())) {
return false;
}
}
}
}
return true;
}
/**
* @return Whether the call has a local result.
*/
static boolean callHasLocalResult(Node n) {
Preconditions.checkState(n.isCall());
return (n.getSideEffectFlags() & Node.FLAG_LOCAL_RESULTS) > 0;
}
/**
* @return Whether the new has a local result.
*/
static boolean newHasLocalResult(Node n) {
Preconditions.checkState(n.isNew());
return n.isOnlyModifiesThisCall();
}
/**
* Returns true if the current node's type implies side effects.
*
* This is a non-recursive version of the may have side effects
* check; used to check wherever the current node's type is one of
* the reason's why a subtree has side effects.
*/
static boolean nodeTypeMayHaveSideEffects
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>(Node n) {
return nodeTypeMayHaveSideEffects(n, null);
}
static boolean nodeTypeMayHaveSideEffects(Node n, AbstractCompiler compiler) {
if (isAssignmentOp(n)) {
return true;
}
switch(n.getType()) {
case Token.DELPROP:
case Token.DEC:
case Token.INC:
case Token.THROW:
return true;
case Token.CALL:
return NodeUtil.functionCallHasSideEffects(n, compiler);
case Token.NEW:
return NodeUtil.constructorCallHasSideEffects(n, compiler);
case Token.NAME:
// A variable definition.
return n.hasChildren();
default:
return false;
}
}
static boolean allArgsUnescapedLocal(Node callOrNew) {
for (Node arg = callOrNew.getFirstChild().getNext();
arg != null; arg = arg.getNext()) {
if (!evaluatesToLocalValue(arg)) {
return false;
}
}
return true;
}
/**
* @return Whether the tree can be affected by side-effects or
* has side-effects.
*/
static boolean canBeSideEffected(Node n) {
Set<String> emptySet = Collections.emptySet();
return canBeSideEffected(n, emptySet);
}
/**
* @param knownConstants A set of names known to be constant value at
* node 'n' (such as locals that are last written before n can execute).
* @return Whether the tree can be affected by side-effects or
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
case Token.FUNCTION:
// Function expression are not changed by side-effects,
// and function declarations are not part of expressions.
Preconditions.checkState(isFunctionExpression(n));
return false;
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
* 3 logical-or ||
* 4 logical-and &&
* 5 bitwise-or |
* 6 bitwise-xor ^
* 7 bitwise-and &
* 8 equality == !=
* 9 relational < <= > >=
* 10 bitwise shift << >> >>>
* 11 addition/subtraction + -
*
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> 12 multiply/divide * / %
* 13 negation/increment ! ~ - ++ --
* 14 call, member () [] .
*/
static int precedence(int type) {
int precedence = precedenceWithDefault(type);
if (precedence != -1) {
return precedence;
}
throw new Error("Unknown precedence for " +
Token.name(type) + " (type " + type + ")");
}
static int precedenceWithDefault(int type) {
switch (type) {
case Token.COMMA: return 0;
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.ASSIGN_URSH:
case Token.ASSIGN_ADD:
case Token.ASSIGN_SUB:
case Token.ASSIGN_MUL:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
case Token.ASSIGN: return 1;
case Token.HOOK: return 2; // ?: operator
case Token.OR: return 3;
case Token.AND: return 4;
case Token.BITOR: return 5;
case Token.BITXOR: return 6;
case Token.BITAND: return 7;
case Token.EQ:
case Token.NE:
case Token.SHEQ:
case Token.SHNE: return 8;
case Token.LT:
case Token.GT:
case Token.LE:
case Token.GE:
case Token.INSTANCEOF:
case Token.IN: return 9;
case Token.LSH:
case Token.RSH:
case Token.URSH: return 10;
case Token.SUB:
case Token.ADD: return 11;
case Token.MUL:
case Token.MOD:
case Token.DIV: return 12;
case Token.INC:
case Token.DEC:
case Token.NEW:
case Token.DELPROP:
case Token.TYPEOF:
case Token.VOID:
case Token.NOT:
case Token.BITNOT:
case Token.POS:
case Token.NEG: return 13;
case Token.CALL:
case Token.GETELEM:
case Token.GETPROP:
// Data values
case Token.ARRAYLIT:
case Token.EMPTY: // TODO(johnlenz): remove this.
case Token.FALSE:
case Token.FUNCTION:
case Token.NAME:
case Token.NULL:
case Token.NUMBER:
case Token.OBJECTLIT:
case Token.REGEXP:
case Token.STRING:
case Token.STRING_KEY:
case Token.THIS:
case Token.TRUE:
return 15;
case Token.CAST:
return 16;
default:
// Statements are lower precedence than expressions.
return -1;
}
}
static boolean isUndefined(Node n) {
switch (n.getType()) {
case Token.VOID:
return true;
case Token.NAME:
return n.getString().equals("undefined");
}
return false;
}
static
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> boolean isNullOrUndefined(Node n) {
return n.isNull() || isUndefined(n);
}
static final Predicate<Node> IMMUTABLE_PREDICATE = new Predicate<Node>() {
@Override
public boolean apply(Node n) {
return isImmutableValue(n);
}
};
static boolean isImmutableResult(Node n) {
return allResultsMatch(n, IMMUTABLE_PREDICATE);
}
/**
* Apply the supplied predicate against
* all possible result Nodes of the expression.
*/
static boolean allResultsMatch(Node n, Predicate<Node> p) {
switch (n.getType()) {
case Token.CAST:
return allResultsMatch(n.getFirstChild(), p);
case Token.ASSIGN:
case Token.COMMA:
return allResultsMatch(n.getLastChild(), p);
case Token.AND:
case Token.OR:
return allResultsMatch(n.getFirstChild(), p)
&& allResultsMatch(n.getLastChild(), p);
case Token.HOOK:
return allResultsMatch(n.getFirstChild().getNext(), p)
&& allResultsMatch(n.getLastChild(), p);
default:
return p.apply(n);
}
}
/**
* Apply the supplied predicate against
* all possible result Nodes of the expression.
*/
static boolean anyResultsMatch(Node n, Predicate<Node> p) {
switch (n.getType()) {
case Token.CAST:
return anyResultsMatch(n.getFirstChild(), p);
case Token.ASSIGN:
case Token.COMMA:
return anyResultsMatch(n.getLastChild(), p);
case Token.AND:
case Token.OR:
return anyResultsMatch(n.getFirstChild(), p)
|| anyResultsMatch(n.getLastChild(), p);
case Token.HOOK:
return anyResultsMatch(n.getFirstChild().getNext(), p)
|| anyResultsMatch(n.getLastChild(), p);
default:
return p.apply(n);
}
}
static class NumbericResultPredicate implements Predicate<Node> {
@Override
public boolean apply(Node n) {
return isNumericResultHelper(n);
}
}
static final NumbericResultPredicate NUMBERIC_RESULT_PREDICATE =
new NumbericResultPredicate();
/**
* Returns true if the result of node evaluation is always a number
*/
static boolean isNumericResult(Node n) {
return allResultsMatch(n, NUMBERIC_RESULT_PREDICATE);
}
static boolean isNumericResultHelper(Node n) {
switch (n.getType()) {
case Token.ADD:
return !mayBeString(n.getFirstChild())
&& !mayBeString(n.getLastChild());
case Token.BITNOT:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
case Token.LSH:
case Token.RSH:
case Token.URSH:
case Token.SUB:
case Token.MUL:
case Token.MOD:
case Token.DIV:
case Token.INC:
case Token.DEC:
case Token.POS:
case Token.NEG:
case Token.NUMBER:
return true;
case Token
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>.NAME:
String name = n.getString();
if (name.equals("NaN")) {
return true;
}
if (name.equals("Infinity")) {
return true;
}
return false;
default:
return false;
}
}
static class BooleanResultPredicate implements Predicate<Node> {
@Override
public boolean apply(Node n) {
return isBooleanResultHelper(n);
}
}
static final BooleanResultPredicate BOOLEAN_RESULT_PREDICATE =
new BooleanResultPredicate();
/**
* @return Whether the result of node evaluation is always a boolean
*/
static boolean isBooleanResult(Node n) {
return allResultsMatch(n, BOOLEAN_RESULT_PREDICATE);
}
static boolean isBooleanResultHelper(Node n) {
switch (n.getType()) {
// Primitives
case Token.TRUE:
case Token.FALSE:
// Comparisons
case Token.EQ:
case Token.NE:
case Token.SHEQ:
case Token.SHNE:
case Token.LT:
case Token.GT:
case Token.LE:
case Token.GE:
// Queries
case Token.IN:
case Token.INSTANCEOF:
// Inversion
case Token.NOT:
// delete operator returns a boolean.
case Token.DELPROP:
return true;
default:
return false;
}
}
static class MayBeStringResultPredicate implements Predicate<Node> {
@Override
public boolean apply(Node n) {
return mayBeStringHelper(n);
}
}
static final MayBeStringResultPredicate MAY_BE_STRING_PREDICATE =
new MayBeStringResultPredicate();
/**
* @returns Whether the results is possibly a string.
*/
static boolean mayBeString(Node n) {
return mayBeString(n, true);
}
static boolean mayBeString(Node n, boolean recurse) {
if (recurse) {
return anyResultsMatch(n, MAY_BE_STRING_PREDICATE);
} else {
return mayBeStringHelper(n);
}
}
static boolean mayBeStringHelper(Node n) {
return !isNumericResult(n) && !isBooleanResult(n)
&& !isUndefined(n) && !n.isNull();
}
/**
* Returns true if the operator is associative.
* e.g. (a * b) * c = a * (b * c)
* Note: "+" is not associative because it is also the concatenation
* for strings. e.g. "a" + (1 + 2) is not "a" + 1 + 2
*/
static boolean isAssociative(int type) {
switch (type) {
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return true;
default:
return false;
}
}
/**
* Returns true if the operator is commutative.
* e.g. (a * b) * c = c * (b * a)
* Note 1: "+" is not commutative because it is also the concatenation
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> * for strings. e.g. "a" + (1 + 2) is not "a" + 1 + 2
* Note 2: only operations on literals and pure functions are commutative.
*/
static boolean isCommutative(int type) {
switch (type) {
case Token.MUL:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return true;
default:
return false;
}
}
static boolean isAssignmentOp(Node n) {
switch (n.getType()){
case Token.ASSIGN:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.ASSIGN_URSH:
case Token.ASSIGN_ADD:
case Token.ASSIGN_SUB:
case Token.ASSIGN_MUL:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
return true;
}
return false;
}
static int getOpFromAssignmentOp(Node n) {
switch (n.getType()){
case Token.ASSIGN_BITOR:
return Token.BITOR;
case Token.ASSIGN_BITXOR:
return Token.BITXOR;
case Token.ASSIGN_BITAND:
return Token.BITAND;
case Token.ASSIGN_LSH:
return Token.LSH;
case Token.ASSIGN_RSH:
return Token.RSH;
case Token.ASSIGN_URSH:
return Token.URSH;
case Token.ASSIGN_ADD:
return Token.ADD;
case Token.ASSIGN_SUB:
return Token.SUB;
case Token.ASSIGN_MUL:
return Token.MUL;
case Token.ASSIGN_DIV:
return Token.DIV;
case Token.ASSIGN_MOD:
return Token.MOD;
}
throw new IllegalArgumentException("Not an assignment op:" + n);
}
/**
* Determines if the given node contains a function statement or function
* expression.
*/
static boolean containsFunction(Node n) {
return containsType(n, Token.FUNCTION);
}
/**
* Returns true if the shallow scope contains references to 'this' keyword
*/
static boolean referencesThis(Node n) {
Node start = (n.isFunction()) ? n.getLastChild() : n;
return containsType(start, Token.THIS, MATCH_NOT_FUNCTION);
}
/**
* Is this a GETPROP or GETELEM node?
*/
static boolean isGet(Node n) {
return n.isGetProp() || n.isGetElem();
}
/**
* Is this node the name of a variable being declared?
*
* @param n The node
* @return True if {@code n} is NAME and {@code parent} is VAR
*/
static boolean isVarDeclaration(Node n) {
// There is no need to verify that parent != null because a NAME node
// always has a parent in a valid parse tree.
return n.isName() && n.getParent().isVar();
}
/**
* For an assignment or variable declaration get the assigned value.
* @return The value node representing the new value.
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>
*/
static Node getAssignedValue(Node n) {
Preconditions.checkState(n.isName());
Node parent = n.getParent();
if (parent.isVar()) {
return n.getFirstChild();
} else if (parent.isAssign() && parent.getFirstChild() == n) {
return n.getNext();
} else {
return null;
}
}
/**
* Is this node an assignment expression statement?
*
* @param n The node
* @return True if {@code n} is EXPR_RESULT and {@code n}'s
* first child is ASSIGN
*/
static boolean isExprAssign(Node n) {
return n.isExprResult()
&& n.getFirstChild().isAssign();
}
/**
* Is this node a call expression statement?
*
* @param n The node
* @return True if {@code n} is EXPR_RESULT and {@code n}'s
* first child is CALL
*/
static boolean isExprCall(Node n) {
return n.isExprResult()
&& n.getFirstChild().isCall();
}
/**
* @return Whether the node represents a FOR-IN loop.
*/
static boolean isForIn(Node n) {
return n.isFor()
&& n.getChildCount() == 3;
}
/**
* Determines whether the given node is a FOR, DO, or WHILE node.
*/
static boolean isLoopStructure(Node n) {
switch (n.getType()) {
case Token.FOR:
case Token.DO:
case Token.WHILE:
return true;
default:
return false;
}
}
/**
* @param n The node to inspect.
* @return If the node, is a FOR, WHILE, or DO, it returns the node for
* the code BLOCK, null otherwise.
*/
static Node getLoopCodeBlock(Node n) {
switch (n.getType()) {
case Token.FOR:
case Token.WHILE:
return n.getLastChild();
case Token.DO:
return n.getFirstChild();
default:
return null;
}
}
/**
* @return Whether the specified node has a loop parent that
* is within the current scope.
*/
static boolean isWithinLoop(Node n) {
for (Node parent : n.getAncestors()) {
if (NodeUtil.isLoopStructure(parent)) {
return true;
}
if (parent.isFunction()) {
break;
}
}
return false;
}
/**
* Determines whether the given node is a FOR, DO, WHILE, WITH, or IF node.
*/
static boolean isControlStructure(Node n) {
switch (n.getType()) {
case Token.FOR:
case Token.DO:
case Token.WHILE:
case Token.WITH:
case Token.IF:
case Token.LABEL:
case Token.TRY:
case Token.CATCH:
case Token.SWITCH:
case Token.CASE:
case Token.DEFAULT_CASE:
return true;
default:
return false;
}
}
/**
* Determines whether the given node is code node for FOR, DO,
* WH
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>ILE, WITH, or IF node.
*/
static boolean isControlStructureCodeBlock(Node parent, Node n) {
switch (parent.getType()) {
case Token.FOR:
case Token.WHILE:
case Token.LABEL:
case Token.WITH:
return parent.getLastChild() == n;
case Token.DO:
return parent.getFirstChild() == n;
case Token.IF:
return parent.getFirstChild() != n;
case Token.TRY:
return parent.getFirstChild() == n || parent.getLastChild() == n;
case Token.CATCH:
return parent.getLastChild() == n;
case Token.SWITCH:
case Token.CASE:
return parent.getFirstChild() != n;
case Token.DEFAULT_CASE:
return true;
default:
Preconditions.checkState(isControlStructure(parent));
return false;
}
}
/**
* Gets the condition of an ON_TRUE / ON_FALSE CFG edge.
* @param n a node with an outgoing conditional CFG edge
* @return the condition node or null if the condition is not obviously a node
*/
static Node getConditionExpression(Node n) {
switch (n.getType()) {
case Token.IF:
case Token.WHILE:
return n.getFirstChild();
case Token.DO:
return n.getLastChild();
case Token.FOR:
switch (n.getChildCount()) {
case 3:
return null;
case 4:
return n.getFirstChild().getNext();
}
throw new IllegalArgumentException("malformed 'for' statement " + n);
case Token.CASE:
return null;
}
throw new IllegalArgumentException(n + " does not have a condition.");
}
/**
* @return Whether the node is of a type that contain other statements.
*/
static boolean isStatementBlock(Node n) {
return n.isScript() || n.isBlock();
}
/**
* @return Whether the node is used as a statement.
*/
static boolean isStatement(Node n) {
return isStatementParent(n.getParent());
}
static boolean isStatementParent(Node parent) {
// It is not possible to determine definitely if a node is a statement
// or not if it is not part of the AST. A FUNCTION node can be
// either part of an expression or a statement.
Preconditions.checkState(parent != null);
switch (parent.getType()) {
case Token.SCRIPT:
case Token.BLOCK:
case Token.LABEL:
return true;
default:
return false;
}
}
/** Whether the node is part of a switch statement. */
static boolean isSwitchCase(Node n) {
return n.isCase() || n.isDefaultCase();
}
/**
* @return Whether the name is a reference to a variable, function or
* function parameter (not a label or a empty function expression name).
*/
static boolean isReferenceName(Node n) {
return n.isName() && !n.getString().isEmpty();
}
/** Whether the child node is the FINALLY block of a try. */
static boolean isTryFinallyNode(Node parent, Node child) {
return parent.isTry() && parent.getChildCount
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>
return false;
}
return (NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == n)
|| (NodeUtil.isForIn(parent) && parent.getFirstChild() == n)
|| parent.isVar()
|| (parent.isFunction() && parent.getFirstChild() == n)
|| parent.isDec()
|| parent.isInc()
|| parent.isParamList()
|| parent.isCatch();
}
/**
* Determines whether a node represents an object literal key
* (e.g. key1 in {key1: value1, key2: value2}).
*
* @param node A node
*/
static boolean isObjectLitKey(Node node) {
switch (node.getType()) {
case Token.STRING_KEY:
case Token.GETTER_DEF:
case Token.SETTER_DEF:
return true;
}
return false;
}
/**
* Get the name of an object literal key.
*
* @param key A node
*/
static String getObjectLitKeyName(Node key) {
switch (key.getType()) {
case Token.STRING_KEY:
case Token.GETTER_DEF:
case Token.SETTER_DEF:
return key.getString();
}
throw new IllegalStateException("Unexpected node type: " + key);
}
/**
* @param key A OBJECTLIT key node.
* @return The type expected when using the key.
*/
static JSType getObjectLitKeyTypeFromValueType(Node key, JSType valueType) {
if (valueType != null) {
switch (key.getType()) {
case Token.GETTER_DEF:
// GET must always return a function type.
if (valueType.isFunctionType()) {
FunctionType fntype = valueType.toMaybeFunctionType();
valueType = fntype.getReturnType();
} else {
return null;
}
break;
case Token.SETTER_DEF:
if (valueType.isFunctionType()) {
// SET must always return a function type.
FunctionType fntype = valueType.toMaybeFunctionType();
Node param = fntype.getParametersNode().getFirstChild();
// SET function must always have one parameter.
valueType = param.getJSType();
} else {
return null;
}
break;
}
}
return valueType;
}
/**
* Determines whether a node represents an object literal get or set key
* (e.g. key1 in {get key1() {}, set key2(a){}).
*
* @param node A node
*/
static boolean isGetOrSetKey(Node node) {
switch (node.getType()) {
case Token.GETTER_DEF:
case Token.SETTER_DEF:
return true;
}
return false;
}
/**
* Converts an operator's token value (see {@link Token}) to a string
* representation.
*
* @param operator the operator's token value to convert
* @return the string representation or {@code null} if the token value is
* not an operator
*/
static String opToStr(int operator) {
switch (operator) {
case Token.BITOR: return "|";
case Token.OR: return
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> redeclareVarsInsideBranch(Node branch) {
Collection<Node> vars = getVarsDeclaredInBranch(branch);
if (vars.isEmpty()) {
return;
}
Node parent = getAddingRoot(branch);
for (Node nameNode : vars) {
Node var = IR.var(
IR.name(nameNode.getString())
.srcref(nameNode))
.srcref(nameNode);
copyNameAnnotations(nameNode, var.getFirstChild());
parent.addChildToFront(var);
}
}
/**
* Copy any annotations that follow a named value.
* @param source
* @param destination
*/
static void copyNameAnnotations(Node source, Node destination) {
if (source.getBooleanProp(Node.IS_CONSTANT_NAME)) {
destination.putBooleanProp(Node.IS_CONSTANT_NAME, true);
}
}
/**
* Gets a Node at the top of the current scope where we can add new var
* declarations as children.
*/
private static Node getAddingRoot(Node n) {
Node addingRoot = null;
Node ancestor = n;
while (null != (ancestor = ancestor.getParent())) {
int type = ancestor.getType();
if (type == Token.SCRIPT) {
addingRoot = ancestor;
break;
} else if (type == Token.FUNCTION) {
addingRoot = ancestor.getLastChild();
break;
}
}
// make sure that the adding root looks ok
Preconditions.checkState(addingRoot.isBlock() ||
addingRoot.isScript());
Preconditions.checkState(addingRoot.getFirstChild() == null ||
!addingRoot.getFirstChild().isScript());
return addingRoot;
}
/**
* Creates a node representing a qualified name.
*
* @param name A qualified name (e.g. "foo" or "foo.bar.baz")
* @return A NAME or GETPROP node
*/
public static Node newQualifiedNameNode(
CodingConvention convention, String name) {
int endPos = name.indexOf('.');
if (endPos == -1) {
return newName(convention, name);
}
Node node;
String nodeName = name.substring(0, endPos);
if ("this".equals(nodeName)) {
node = IR.thisNode();
} else {
node = newName(convention, nodeName);
}
int startPos;
do {
startPos = endPos + 1;
endPos = name.indexOf('.', startPos);
String part = (endPos == -1
? name.substring(startPos)
: name.substring(startPos, endPos));
Node propNode = IR.string(part);
if (convention.isConstantKey(part)) {
propNode.putBooleanProp(Node.IS_CONSTANT_NAME, true);
}
node = IR.getprop(node, propNode);
} while (endPos != -1);
return node;
}
/**
* Creates a node representing a qualified name.
*
* @param name A qualified name (e.g. "foo" or "foo.bar.baz")
* @return A NAME or GETPROP node
*/
public static Node newQualifiedNameNodeDeclaration(
CodingConvention convention, String name
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>.isVar()) {
String name = n.getString();
if (!vars.containsKey(name)) {
vars.put(name, n);
}
}
}
}
}
/**
* Retrieves vars declared in the current node tree, excluding descent scopes.
*/
static Collection<Node> getVarsDeclaredInBranch(Node root) {
VarCollector collector = new VarCollector();
visitPreOrder(
root,
collector,
MATCH_NOT_FUNCTION);
return collector.vars.values();
}
/**
* @return {@code true} if the node an assignment to a prototype property of
* some constructor.
*/
static boolean isPrototypePropertyDeclaration(Node n) {
return isExprAssign(n) &&
isPrototypeProperty(n.getFirstChild().getFirstChild());
}
/**
* @return Whether the node represents a qualified prototype property.
*/
static boolean isPrototypeProperty(Node n) {
String lhsString = n.getQualifiedName();
return lhsString != null && lhsString.contains(".prototype.");
}
/**
* @return The class name part of a qualified prototype name.
*/
static Node getPrototypeClassName(Node qName) {
Node cur = qName;
while (cur.isGetProp()) {
if (cur.getLastChild().getString().equals("prototype")) {
return cur.getFirstChild();
} else {
cur = cur.getFirstChild();
}
}
return null;
}
/**
* @return The string property name part of a qualified prototype name.
*/
static String getPrototypePropertyName(Node qName) {
String qNameStr = qName.getQualifiedName();
int prototypeIdx = qNameStr.lastIndexOf(".prototype.");
int memberIndex = prototypeIdx + ".prototype".length() + 1;
return qNameStr.substring(memberIndex);
}
/**
* Create a node for an empty result expression:
* "void 0"
*/
static Node newUndefinedNode(Node srcReferenceNode) {
Node node = IR.voidNode(IR.number(0));
if (srcReferenceNode != null) {
node.copyInformationFromForTree(srcReferenceNode);
}
return node;
}
/**
* Create a VAR node containing the given name and initial value expression.
*/
static Node newVarNode(String name, Node value) {
Node nodeName = IR.name(name);
if (value != null) {
Preconditions.checkState(value.getNext() == null);
nodeName.addChildToBack(value);
nodeName.srcref(value);
}
Node var = IR.var(nodeName).srcref(nodeName);
return var;
}
/**
* A predicate for matching name nodes with the specified node.
*/
private static class MatchNameNode implements Predicate<Node>{
final String name;
MatchNameNode(String name){
this.name = name;
}
@Override
public boolean apply(Node n) {
return n.isName() && n.getString().equals(name);
}
}
/**
* A predicate for matching nodes with the specified type.
*/
static class MatchNodeType implements Predicate<Node>{
final int type;
MatchNodeType(int type){
this.type = type
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>;
}
@Override
public boolean apply(Node n) {
return n.getType() == type;
}
}
/**
* A predicate for matching var or function declarations.
*/
static class MatchDeclaration implements Predicate<Node> {
@Override
public boolean apply(Node n) {
return isFunctionDeclaration(n) || n.isVar();
}
}
/**
* A predicate for matching anything except function nodes.
*/
private static class MatchNotFunction implements Predicate<Node>{
@Override
public boolean apply(Node n) {
return !n.isFunction();
}
}
static final Predicate<Node> MATCH_NOT_FUNCTION = new MatchNotFunction();
/**
* A predicate for matching statements without exiting the current scope.
*/
static class MatchShallowStatement implements Predicate<Node>{
@Override
public boolean apply(Node n) {
Node parent = n.getParent();
return n.isBlock()
|| (!n.isFunction() && (parent == null
|| isControlStructure(parent)
|| isStatementBlock(parent)));
}
}
/**
* Finds the number of times a type is referenced within the node tree.
*/
static int getNodeTypeReferenceCount(
Node node, int type, Predicate<Node> traverseChildrenPred) {
return getCount(node, new MatchNodeType(type), traverseChildrenPred);
}
/**
* Whether a simple name is referenced within the node tree.
*/
static boolean isNameReferenced(Node node,
String name,
Predicate<Node> traverseChildrenPred) {
return has(node, new MatchNameNode(name), traverseChildrenPred);
}
/**
* Whether a simple name is referenced within the node tree.
*/
static boolean isNameReferenced(Node node, String name) {
return isNameReferenced(node, name, Predicates.<Node>alwaysTrue());
}
/**
* Finds the number of times a simple name is referenced within the node tree.
*/
static int getNameReferenceCount(Node node, String name) {
return getCount(
node, new MatchNameNode(name), Predicates.<Node>alwaysTrue());
}
/**
* @return Whether the predicate is true for the node or any of its children.
*/
static boolean has(Node node,
Predicate<Node> pred,
Predicate<Node> traverseChildrenPred) {
if (pred.apply(node)) {
return true;
}
if (!traverseChildrenPred.apply(node)) {
return false;
}
for (Node c = node.getFirstChild(); c != null; c = c.getNext()) {
if (has(c, pred, traverseChildrenPred)) {
return true;
}
}
return false;
}
/**
* @return The number of times the the predicate is true for the node
* or any of its children.
*/
static int getCount(
Node n, Predicate<Node> pred, Predicate<Node> traverseChildrenPred) {
int total = 0;
if (pred.apply(n)) {
total++;
}
if (traverseChildrenPred.apply(n)) {
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
total += getCount(c, pred
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> {
return convention.isConstantKey(node.getString());
} else if (node.isName()) {
return convention.isConstant(node.getString());
}
return false;
}
/**
* Get the JSDocInfo for a function.
*/
public static JSDocInfo getFunctionJSDocInfo(Node n) {
Preconditions.checkState(n.isFunction());
JSDocInfo fnInfo = n.getJSDocInfo();
if (fnInfo == null && NodeUtil.isFunctionExpression(n)) {
// Look for the info on other nodes.
Node parent = n.getParent();
if (parent.isAssign()) {
// on ASSIGNs
fnInfo = parent.getJSDocInfo();
} else if (parent.isName()) {
// on var NAME = function() { ... };
fnInfo = parent.getParent().getJSDocInfo();
}
}
return fnInfo;
}
/**
* @param n The node.
* @return The source name property on the node or its ancestors.
*/
public static String getSourceName(Node n) {
String sourceName = null;
while (sourceName == null && n != null) {
sourceName = n.getSourceFileName();
n = n.getParent();
}
return sourceName;
}
/**
* @param n The node.
* @return The source name property on the node or its ancestors.
*/
public static StaticSourceFile getSourceFile(Node n) {
StaticSourceFile sourceName = null;
while (sourceName == null && n != null) {
sourceName = n.getStaticSourceFile();
n = n.getParent();
}
return sourceName;
}
/**
* @param n The node.
* @return The InputId property on the node or its ancestors.
*/
public static InputId getInputId(Node n) {
while (n != null && !n.isScript()) {
n = n.getParent();
}
return (n != null && n.isScript()) ? n.getInputId() : null;
}
/**
* A new CALL node with the "FREE_CALL" set based on call target.
*/
static Node newCallNode(Node callTarget, Node... parameters) {
boolean isFreeCall = !isGet(callTarget);
Node call = IR.call(callTarget);
call.putBooleanProp(Node.FREE_CALL, isFreeCall);
for (Node parameter : parameters) {
call.addChildToBack(parameter);
}
return call;
}
/**
* @return Whether the node is known to be a value that is not referenced
* elsewhere.
*/
static boolean evaluatesToLocalValue(Node value) {
return evaluatesToLocalValue(value, Predicates.<Node>alwaysFalse());
}
/**
* @param locals A predicate to apply to unknown local values.
* @return Whether the node is known to be a value that is not a reference
* outside the expression scope.
*/
static boolean evaluatesToLocalValue(Node value, Predicate<Node> locals) {
switch (value.getType()) {
case Token.CAST:
return evaluatesToLocalValue(value.getFirstChild(), locals);
case Token.
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> if no such argument exists.
*/
static Node getArgumentForCallOrNew(Node call, int index) {
Preconditions.checkState(isCallOrNew(call));
return getNthSibling(
call.getFirstChild().getNext(), index);
}
/**
* Returns whether this is a target of a call or new.
*/
static boolean isCallOrNewTarget(Node target) {
Node parent = target.getParent();
return parent != null
&& NodeUtil.isCallOrNew(parent)
&& parent.getFirstChild() == target;
}
private static boolean isToStringMethodCall(Node call) {
Node getNode = call.getFirstChild();
if (isGet(getNode)) {
Node propNode = getNode.getLastChild();
return propNode.isString() && "toString".equals(propNode.getString());
}
return false;
}
/** Find the best JSDoc for the given node. */
static JSDocInfo getBestJSDocInfo(Node n) {
JSDocInfo info = n.getJSDocInfo();
if (info == null) {
Node parent = n.getParent();
if (parent == null) {
return null;
}
if (parent.isName()) {
return getBestJSDocInfo(parent);
} else if (parent.isAssign()) {
return getBestJSDocInfo(parent);
} else if (isObjectLitKey(parent)) {
return parent.getJSDocInfo();
} else if (parent.isFunction()) {
return parent.getJSDocInfo();
} else if (parent.isVar() && parent.hasOneChild()) {
return parent.getJSDocInfo();
} else if ((parent.isHook() && parent.getFirstChild() != n) ||
parent.isOr() ||
parent.isAnd() ||
(parent.isComma() && parent.getFirstChild() != n)) {
return getBestJSDocInfo(parent);
} else if (parent.isCast()) {
return parent.getJSDocInfo();
}
}
return info;
}
/** Find the l-value that the given r-value is being assigned to. */
static Node getBestLValue(Node n) {
Node parent = n.getParent();
boolean isFunctionDeclaration = isFunctionDeclaration(n);
if (isFunctionDeclaration) {
return n.getFirstChild();
} else if (parent.isName()) {
return parent;
} else if (parent.isAssign()) {
return parent.getFirstChild();
} else if (isObjectLitKey(parent)) {
return parent;
} else if (
(parent.isHook() && parent.getFirstChild() != n) ||
parent.isOr() ||
parent.isAnd() ||
(parent.isComma() && parent.getFirstChild() != n)) {
return getBestLValue(parent);
} else if (parent.isCast()) {
return getBestLValue(parent);
}
return null;
}
/** Gets the r-value of a node returned by getBestLValue. */
static Node getRValueOfLValue(Node n) {
Node parent = n.getParent();
switch (parent.getType()) {
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> case Token.ASSIGN:
return n.getNext();
case Token.VAR:
return n.getFirstChild();
case Token.FUNCTION:
return parent;
}
return null;
}
/** Get the owner of the given l-value node. */
static Node getBestLValueOwner(@Nullable Node lValue) {
if (lValue == null || lValue.getParent() == null) {
return null;
}
if (isObjectLitKey(lValue)) {
return getBestLValue(lValue.getParent());
} else if (isGet(lValue)) {
return lValue.getFirstChild();
}
return null;
}
/** Get the name of the given l-value node. */
static String getBestLValueName(@Nullable Node lValue) {
if (lValue == null || lValue.getParent() == null) {
return null;
}
if (isObjectLitKey(lValue)) {
Node owner = getBestLValue(lValue.getParent());
if (owner != null) {
String ownerName = getBestLValueName(owner);
if (ownerName != null) {
return ownerName + "." + getObjectLitKeyName(lValue);
}
}
return null;
}
return lValue.getQualifiedName();
}
/**
* @returns false iff the result of the expression is not consumed.
*/
static boolean isExpressionResultUsed(Node expr) {
// TODO(johnlenz): consider sharing some code with trySimpleUnusedResult.
Node parent = expr.getParent();
switch (parent.getType()) {
case Token.BLOCK:
case Token.EXPR_RESULT:
return false;
case Token.CAST:
return isExpressionResultUsed(parent);
case Token.HOOK:
case Token.AND:
case Token.OR:
return (expr == parent.getFirstChild())
? true : isExpressionResultUsed(parent);
case Token.COMMA:
Node gramps = parent.getParent();
if (gramps.isCall() &&
parent == gramps.getFirstChild()) {
// Semantically, a direct call to eval is different from an indirect
// call to an eval. See ECMA-262 S15.1.2.1. So it's OK for the first
// expression to a comma to be a no-op if it's used to indirect
// an eval. This we pretend that this is "used".
if (expr == parent.getFirstChild() &&
parent.getChildCount() == 2 &&
expr.getNext().isName() &&
"eval".equals(expr.getNext().getString())) {
return true;
}
}
return (expr == parent.getFirstChild())
? false : isExpressionResultUsed(parent);
case Token.FOR:
if (!NodeUtil.isForIn(parent)) {
// Only an expression whose result is in the condition part of the
// expression is used.
return (parent.getChildAtIndex(1) == expr);
}
break;
}
return true;
}
/**
* @param n The expression to check.
* @return Whether the expression is unconditionally executed only once in the
* containing execution scope.
*/
static boolean isExecutedExactlyOnce(Node
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> n) {
inspect: do {
Node parent = n.getParent();
switch (parent.getType()) {
case Token.IF:
case Token.HOOK:
case Token.AND:
case Token.OR:
if (parent.getFirstChild() != n) {
return false;
}
// other ancestors may be conditional
continue inspect;
case Token.FOR:
if (NodeUtil.isForIn(parent)) {
if (parent.getChildAtIndex(1) != n) {
return false;
}
} else {
if (parent.getFirstChild() != n) {
return false;
}
}
// other ancestors may be conditional
continue inspect;
case Token.WHILE:
case Token.DO:
return false;
case Token.TRY:
// Consider all code under a try/catch to be conditionally executed.
if (!hasFinally(parent) || parent.getLastChild() != n) {
return false;
}
continue inspect;
case Token.CASE:
case Token.DEFAULT_CASE:
return false;
case Token.SCRIPT:
case Token.FUNCTION:
// Done, we've reached the scope root.
break inspect;
}
} while ((n = n.getParent()) != null);
return true;
}
/**
* @return An appropriate AST node for the boolean value.
*/
static Node booleanNode(boolean value) {
return value ? IR.trueNode() : IR.falseNode();
}
/**
* @return An appropriate AST node for the double value.
*/
static Node numberNode(double value, Node srcref) {
Node result;
if (Double.isNaN(value)) {
result = IR.name("NaN");
} else if (value == Double.POSITIVE_INFINITY) {
result = IR.name("Infinity");
} else if (value == Double.NEGATIVE_INFINITY) {
result = IR.neg(IR.name("Infinity"));
} else {
result = IR.number(value);
}
if (srcref != null) {
result.srcrefTree(srcref);
}
return result;
}
static boolean isNaN(Node n) {
if ((n.isName() && n.getString().equals("NaN")) ||
(n.getType() == Token.DIV &&
n.getFirstChild().isNumber() && n.getFirstChild().getDouble() == 0 &&
n.getLastChild().isNumber() && n.getLastChild().getDouble() == 0)) {
return true;
}
return false;
}
/**
* Given an AST and its copy, map the root node of each scope of main to the
* corresponding root node of clone
*/
public static Map<Node, Node> mapMainToClone(Node main, Node clone) {
Preconditions.checkState(main.isEquivalentTo(clone));
Map<Node, Node> mtoc = new HashMap<Node, Node>();
mtoc.put(main, clone);
mtocHelper(mtoc, main, clone);
return mtoc;
}
private static void mtocHelper(Map<Node, Node> map, Node main, Node clone) {
if (main.isFunction()) {
map.put(main, clone);
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> = true;
NodeTraversal.traverse(compiler, externs, this);
}
if (root != null) {
inExterns = false;
NodeTraversal.traverse(compiler, root, this);
}
}
@Override
public void hotSwapScript(Node root, Node originalRoot) {
Preconditions.checkNotNull(root);
Preconditions.checkState(root.isScript());
inExterns = false;
NodeTraversal.traverse(compiler, root, this);
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
JSDocInfo docInfo;
switch (n.getType()) {
// Infer JSDocInfo on types of all type declarations on variables.
case Token.NAME:
if (parent == null) {
return;
}
// Only allow JSDoc on VARs, function declarations, and assigns.
if (!parent.isVar() &&
!NodeUtil.isFunctionDeclaration(parent) &&
!(parent.isAssign() &&
n == parent.getFirstChild())) {
return;
}
// There are four places the doc info could live.
// 1) A FUNCTION node.
// /** ... */ function f() { ... }
// 2) An ASSIGN parent.
// /** ... */ x = function () { ... }
// 3) A NAME parent.
// var x, /** ... */ y = function() { ... }
// 4) A VAR gramps.
// /** ... */ var x = function() { ... }
docInfo = n.getJSDocInfo();
if (docInfo == null &&
!(parent.isVar() &&
!parent.hasOneChild())) {
docInfo = parent.getJSDocInfo();
}
// Try to find the type of the NAME.
JSType varType = n.getJSType();
if (varType == null && parent.isFunction()) {
varType = parent.getJSType();
}
// If we have no type to attach JSDocInfo to, then there's nothing
// we can do.
if (varType == null || docInfo == null) {
return;
}
// Dereference the type. If the result is not an object, or already
// has docs attached, then do nothing.
ObjectType objType = dereferenceToObject(varType);
if (objType == null || objType.getJSDocInfo() != null) {
return;
}
attachJSDocInfoToNominalTypeOrShape(objType, docInfo, n.getString());
break;
case Token.GETPROP:
// Infer JSDocInfo on properties.
// There are two ways to write doc comments on a property.
//
// 1)
// /** @deprecated */
// obj.prop = ...
//
// 2)
// /** @deprecated */
// obj.prop;
if (parent.isExprResult() ||
(parent.isAssign() &&
parent.getFirstChild() == n)) {
docInfo = n.getJSDocInfo();
if (docInfo == null) {
docInfo = parent.getJSDocInfo();
}
if (docInfo != null) {
ObjectType lhsType =
dere
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>ferenceToObject(n.getFirstChild().getJSType());
if (lhsType != null) {
// Put the JSDoc in the property slot, if there is one.
String propName = n.getLastChild().getString();
if (lhsType.hasOwnProperty(propName)) {
lhsType.setPropertyJSDocInfo(propName, docInfo);
}
// Put the JSDoc in any constructors or function shapes as well.
ObjectType propType =
dereferenceToObject(lhsType.getPropertyType(propName));
if (propType != null) {
attachJSDocInfoToNominalTypeOrShape(
propType, docInfo, n.getQualifiedName());
}
}
}
}
break;
}
}
/**
* Dereferences the given type to an object, or returns null.
*/
private ObjectType dereferenceToObject(JSType type) {
return ObjectType.cast(type == null ? null : type.dereference());
}
/**
* Handle cases #1 and #3 in the class doc.
*/
private void attachJSDocInfoToNominalTypeOrShape(
ObjectType objType, JSDocInfo docInfo, @Nullable String qName) {
if (objType.isConstructor() ||
objType.isEnumType() ||
objType.isInterface()) {
// Named types.
if (objType.hasReferenceName() &&
objType.getReferenceName().equals(qName)) {
objType.setJSDocInfo(docInfo);
if (objType.isConstructor() || objType.isInterface()) {
JSType.toMaybeFunctionType(objType).getInstanceType().setJSDocInfo(
docInfo);
} else if (objType instanceof EnumType) {
((EnumType) objType).getElementsType().setJSDocInfo(docInfo);
}
}
} else if (!objType.isNativeObjectType() &&
objType.isFunctionType()) {
// Structural functions.
objType.setJSDocInfo(docInfo);
}
}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> @param properties A map of all the properties of this record type.
* @param declared Whether this is a declared or synthesized type.
* A synthesized record type is just used for bookkeeping
* in the type system. A declared record type was actually used in the
* user's program.
* @throws IllegalStateException if the {@code RecordProperty} associated
* with a property is null.
*/
RecordType(JSTypeRegistry registry, Map<String, RecordProperty> properties,
boolean declared) {
super(registry, null, null);
setPrettyPrint(true);
this.declared = declared;
for (String property : properties.keySet()) {
RecordProperty prop = properties.get(property);
if (prop == null) {
throw new IllegalStateException(
"RecordProperty associated with a property should not be null!");
}
if (declared) {
defineDeclaredProperty(
property, prop.getType(), prop.getPropertyNode());
} else {
defineSynthesizedProperty(
property, prop.getType(), prop.getPropertyNode());
}
}
// Freeze the record type.
isFrozen = true;
}
/** @return Is this synthesized for internal bookkeeping? */
boolean isSynthetic() {
return !declared;
}
boolean checkRecordEquivalenceHelper(
RecordType otherRecord, EquivalenceMethod eqMethod) {
Set<String> keySet = getOwnPropertyNames();
Set<String> otherKeySet = otherRecord.getOwnPropertyNames();
if (!otherKeySet.equals(keySet)) {
return false;
}
for (String key : keySet) {
if (!otherRecord.getPropertyType(key).checkEquivalenceHelper(
getPropertyType(key), eqMethod)) {
return false;
}
}
return true;
}
@Override
public ObjectType getImplicitPrototype() {
return registry.getNativeObjectType(JSTypeNative.OBJECT_TYPE);
}
@Override
boolean defineProperty(String propertyName, JSType type,
boolean inferred, Node propertyNode) {
if (isFrozen) {
return false;
}
return super.defineProperty(propertyName, type, inferred,
propertyNode);
}
JSType getGreatestSubtypeHelper(JSType that) {
if (that.isRecordType()) {
RecordType thatRecord = that.toMaybeRecordType();
RecordTypeBuilder builder = new RecordTypeBuilder(registry);
builder.setSynthesized(true);
// The greatest subtype consists of those *unique* properties of both
// record types. If any property conflicts, then the NO_TYPE type
// is returned.
for (String property : getOwnPropertyNames()) {
if (thatRecord.hasProperty(property) &&
!thatRecord.getPropertyType(property).isInvariant(
getPropertyType(property))) {
return registry.getNativeObjectType(JSTypeNative.NO_TYPE);
}
builder.addProperty(property, getPropertyType(property),
getPropertyNode(property));
}
for (String property : thatRecord.getOwnPropertyNames()) {
if (!hasProperty(property)) {
builder.addProperty(property, thatRecord.getPropertyType(property),
thatRecord.getPropertyNode(property));
}
}
return builder.build();
}
JSType greatestSubtype = registry.
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>/*
* Copyright 2004 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterators;
import com.google.javascript.rhino.ErrorReporter;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.ObjectType;
import com.google.javascript.rhino.jstype.StaticReference;
import com.google.javascript.rhino.jstype.StaticScope;
import com.google.javascript.rhino.jstype.StaticSlot;
import com.google.javascript.rhino.jstype.StaticSourceFile;
import com.google.javascript.rhino.jstype.StaticSymbolTable;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Scope contains information about a variable scope in JavaScript.
* Scopes can be nested, a scope points back to its parent scope.
* A Scope contains information about variables defined in that scope.
* <p>
* A Scope is also used as a lattice element for flow-sensitive type inference.
* As a lattice element, a Scope is viewed as a map from names to types. A name
* not in the map is considered to have the bottom type. The join of two maps m1
* and m2 is the map of the union of names with {@link JSType#getLeastSupertype}
* to meet the m1 type and m2 type.
*
* @see NodeTraversal
* @see DataFlowAnalysis
*
*/
public class Scope
implements StaticScope<JSType>, StaticSymbolTable<Scope.Var, Scope.Var> {
private final Map<String, Var> vars = new LinkedHashMap<String, Var>();
private final Scope parent;
private final int depth;
private final Node rootNode;
/** Whether this is a bottom scope for the purposes of type inference. */
private final boolean isBottom;
private Var arguments;
private static final Predicate<Var> DECLARATIVELY_UNBOUND_VARS_WITHOUT_TYPES =
new Predicate<Var>() {
@Override public boolean apply(Var var) {
return var.getParentNode() != null &&
var.getType() == null && // no declared type
var
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>.getParentNode().isVar() &&
!var.isExtern();
}
};
/** Stores info about a variable */
public static class Var
implements StaticSlot<JSType>, StaticReference<JSType> {
/** name */
final String name;
/** Var node */
final Node nameNode;
/**
* The variable's type.
*/
private JSType type;
/**
* Whether the variable's type has been inferred or is declared. An inferred
* type may change over time (as more code is discovered), whereas a
* declared type is a static contract that must be matched.
*/
private final boolean typeInferred;
/** Input source */
final CompilerInput input;
/**
* The index at which the var is declared. e..g if it's 0, it's the first
* declared variable in that scope
*/
final int index;
/** The enclosing scope */
final Scope scope;
/** @see #isMarkedEscaped */
private boolean markedEscaped = false;
/** @see #isMarkedAssignedExactlyOnce */
private boolean markedAssignedExactlyOnce = false;
/**
* Creates a variable.
*
* @param inferred whether its type is inferred (as opposed to declared)
*/
private Var(boolean inferred, String name, Node nameNode, JSType type,
Scope scope, int index, CompilerInput input) {
this.name = name;
this.nameNode = nameNode;
this.type = type;
this.scope = scope;
this.index = index;
this.input = input;
this.typeInferred = inferred;
}
/**
* Gets the name of the variable.
*/
@Override
public String getName() {
return name;
}
/**
* Gets the node for the name of the variable.
*/
@Override
public Node getNode() {
return nameNode;
}
CompilerInput getInput() {
return input;
}
@Override
public StaticSourceFile getSourceFile() {
return nameNode.getStaticSourceFile();
}
@Override
public Var getSymbol() {
return this;
}
@Override
public Var getDeclaration() {
return nameNode == null ? null : this;
}
/**
* Gets the parent of the name node.
*/
public Node getParentNode() {
return nameNode == null ? null : nameNode.getParent();
}
/**
* Whether this is a bleeding function (an anonymous named function
* that bleeds into the inner scope).
*/
public boolean isBleedingFunction() {
return NodeUtil.isFunctionExpression(getParentNode());
}
/**
* Gets the scope where this variable is declared.
*/
Scope getScope() {
return scope;
}
/**
* Returns whether this is a global variable.
*/
public boolean isGlobal() {
return scope.isGlobal();
}
/**
* Returns whether this is a local variable.
*/
public boolean isLocal() {
return scope.isLocal();
}
/**
* Returns whether this is defined in an extern file.
*/
boolean isExtern() {
return input == null || input.isExtern();
}
/**
* Returns {@code true} if the variable is declared as a constant
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>,
* based on the value reported by {@code NodeUtil}.
*/
public boolean isConst() {
return nameNode != null && NodeUtil.isConstantName(nameNode);
}
/**
* Returns {@code true} if the variable is declared as a define.
* A variable is a define if it is annotated by {@code @define}.
*/
public boolean isDefine() {
JSDocInfo info = getJSDocInfo();
return info != null && info.isDefine();
}
public Node getInitialValue() {
return NodeUtil.getRValueOfLValue(nameNode);
}
/**
* Gets this variable's type. To know whether this type has been inferred,
* see {@code #isTypeInferred()}.
*/
@Override
public JSType getType() {
return type;
}
/**
* Returns the name node that produced this variable.
*/
public Node getNameNode() {
return nameNode;
}
/**
* Gets the JSDocInfo for the variable.
*/
@Override
public JSDocInfo getJSDocInfo() {
return nameNode == null ? null : NodeUtil.getBestJSDocInfo(nameNode);
}
/**
* Sets this variable's type.
* @throws IllegalStateException if the variable's type is not inferred
*/
void setType(JSType type) {
Preconditions.checkState(isTypeInferred());
this.type = type;
}
/**
* Resolve this variable's type.
*/
void resolveType(ErrorReporter errorReporter) {
if (type != null) {
type = type.resolve(errorReporter, scope);
}
}
/**
* Returns whether this variable's type is inferred. To get the variable's
* type, see {@link #getType()}.
*/
@Override
public boolean isTypeInferred() {
return typeInferred;
}
public String getInputName() {
if (input == null) {
return "<non-file>";
}
return input.getName();
}
public boolean isNoShadow() {
JSDocInfo info = getJSDocInfo();
return info != null && info.isNoShadow();
}
@Override public boolean equals(Object other) {
if (!(other instanceof Var)) {
return false;
}
Var otherVar = (Var) other;
return otherVar.nameNode == nameNode;
}
@Override public int hashCode() {
return nameNode.hashCode();
}
@Override
public String toString() {
return "Scope.Var " + name + "{" + type + "}";
}
/**
* Record that this is escaped by an inner scope.
*
* In other words, it's assigned in an inner scope so that it's much harder
* to make assertions about its value at a given point.
*/
void markEscaped() {
markedEscaped = true;
}
/**
* Whether this is escaped by an inner scope.
* Notice that not all scope creators record this information.
*/
boolean isMarkedEscaped() {
return markedEscaped;
}
/**
* Record that this is assigned exactly once..
*
* In other words, it's assigned in an inner scope so that it's much
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> harder
* to make assertions about its value at a given point.
*/
void markAssignedExactlyOnce() {
markedAssignedExactlyOnce = true;
}
/**
* Whether this is assigned exactly once.
* Notice that not all scope creators record this information.
*/
boolean isMarkedAssignedExactlyOnce() {
return markedAssignedExactlyOnce;
}
}
/**
* A special subclass of Var used to distinguish "arguments" in the current
* scope.
*/
// TODO(johnlenz): Include this the list of Vars for the scope.
public static class Arguments extends Var {
Arguments(Scope scope) {
super(
false, // no inferred
"arguments", // always arguments
null, // no declaration node
// TODO(johnlenz): provide the type of "Arguments".
null, // no type info
scope,
-1, // no variable index
null // input
);
}
@Override public boolean equals(Object other) {
if (!(other instanceof Arguments)) {
return false;
}
Arguments otherVar = (Arguments) other;
return otherVar.scope.getRootNode() == scope.getRootNode();
}
@Override public int hashCode() {
return System.identityHashCode(this);
}
}
/**
* Creates a Scope given the parent Scope and the root node of the scope.
* @param parent The parent Scope. Cannot be null.
* @param rootNode Typically the FUNCTION node.
*/
Scope(Scope parent, Node rootNode) {
Preconditions.checkNotNull(parent);
Preconditions.checkArgument(rootNode != parent.rootNode);
this.parent = parent;
this.rootNode = rootNode;
this.isBottom = false;
this.depth = parent.depth + 1;
}
/**
* Creates a empty Scope (bottom of the lattice).
* @param rootNode Typically a FUNCTION node or the global BLOCK node.
* @param isBottom Whether this is the bottom of a lattice. Otherwise,
* it must be a global scope.
*/
private Scope(Node rootNode, boolean isBottom) {
this.parent = null;
this.rootNode = rootNode;
this.isBottom = isBottom;
this.depth = 0;
}
static Scope createGlobalScope(Node rootNode) {
return new Scope(rootNode, false);
}
static Scope createLatticeBottom(Node rootNode) {
return new Scope(rootNode, true);
}
/** The depth of the scope. The global scope has depth 0. */
int getDepth() {
return depth;
}
/** Whether this is the bottom of the lattice. */
boolean isBottom() {
return isBottom;
}
/**
* Gets the container node of the scope. This is typically the FUNCTION
* node or the global BLOCK/SCRIPT node.
*/
@Override
public Node getRootNode() {
return rootNode;
}
public Scope getParent() {
return parent;
}
Scope getGlobalScope() {
Scope result = this;
while (result.getParent() != null) {
result = result.getParent();
}
return result;
}
@Override
public StaticScope<JSType> getParentScope() {
return
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> parent;
}
/**
* Gets the type of {@code this} in the current scope.
*/
@Override
public JSType getTypeOfThis() {
if (isGlobal()) {
return ObjectType.cast(rootNode.getJSType());
}
Preconditions.checkState(rootNode.isFunction());
JSType nodeType = rootNode.getJSType();
if (nodeType != null && nodeType.isFunctionType()) {
return nodeType.toMaybeFunctionType().getTypeOfThis();
} else {
return parent.getTypeOfThis();
}
}
/**
* Declares a variable whose type is inferred.
*
* @param name name of the variable
* @param nameNode the NAME node declaring the variable
* @param type the variable's type
* @param input the input in which this variable is defined.
*/
Var declare(String name, Node nameNode, JSType type, CompilerInput input) {
return declare(name, nameNode, type, input, true);
}
/**
* Declares a variable.
*
* @param name name of the variable
* @param nameNode the NAME node declaring the variable
* @param type the variable's type
* @param input the input in which this variable is defined.
* @param inferred Whether this variable's type is inferred (as opposed
* to declared).
*/
Var declare(String name, Node nameNode,
JSType type, CompilerInput input, boolean inferred) {
Preconditions.checkState(name != null && name.length() > 0);
// Make sure that it's declared only once
Preconditions.checkState(vars.get(name) == null);
Var var = new Var(inferred, name, nameNode, type, this, vars.size(), input);
vars.put(name, var);
return var;
}
/**
* Undeclares a variable, to be used when the compiler optimizes out
* a variable and removes it from the scope.
*/
void undeclare(Var var) {
Preconditions.checkState(var.scope == this);
Preconditions.checkState(vars.get(var.name) == var);
vars.remove(var.name);
}
@Override
public Var getSlot(String name) {
return getVar(name);
}
@Override
public Var getOwnSlot(String name) {
return vars.get(name);
}
/**
* Returns the variable, may be null
*/
public Var getVar(String name) {
Var var = vars.get(name);
if (var != null) {
return var;
} else if (parent != null) { // Recurse up the parent Scope
return parent.getVar(name);
} else {
return null;
}
}
/**
* Get a unique VAR object to represents "arguments" within this scope
*/
public Var getArgumentsVar() {
if (arguments == null) {
arguments = new Arguments(this);
}
return arguments;
}
/**
* Returns true if a variable is declared.
*/
public boolean isDeclared(String name, boolean recurse) {
Scope scope = this;
if (scope.vars.containsKey(name)) {
return true;
}
if
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> (scope.parent != null && recurse) {
return scope.parent.isDeclared(name, recurse);
}
return false;
}
/**
* Return an iterator over all of the variables declared in this scope.
*/
public Iterator<Var> getVars() {
return vars.values().iterator();
}
/**
* Return an iterable over all of the variables declared in this scope.
*/
Iterable<Var> getVarIterable() {
return vars.values();
}
@Override
public Iterable<Var> getReferences(Var var) {
return ImmutableList.of(var);
}
@Override
public StaticScope<JSType> getScope(Var var) {
return var.scope;
}
@Override
public Iterable<Var> getAllSymbols() {
return Collections.unmodifiableCollection(vars.values());
}
/**
* Returns number of variables in this scope
*/
public int getVarCount() {
return vars.size();
}
/**
* Returns whether this is the global scope.
*/
public boolean isGlobal() {
return parent == null;
}
/**
* Returns whether this is a local scope (i.e. not the global scope).
*/
public boolean isLocal() {
return parent != null;
}
/**
* Gets all variables declared with "var" but without declared types attached.
*/
public Iterator<Var> getDeclarativelyUnboundVarsWithoutTypes() {
return Iterators.filter(
getVars(), DECLARATIVELY_UNBOUND_VARS_WITHOUT_TYPES);
}
static interface TypeResolver {
void resolveTypes();
}
private TypeResolver typeResolver;
/** Resolve all type references. Only used on typed scopes. */
void resolveTypes() {
if (typeResolver != null) {
typeResolver.resolveTypes();
typeResolver = null;
}
}
void setTypeResolver(TypeResolver resolver) {
this.typeResolver = resolver;
}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>Override
public BooleanLiteralSet getPossibleToBooleanOutcomes() {
return BooleanLiteralSet.BOTH;
}
@Override
JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) {
return this;
}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> {
Node callNode = functionCall.callNode;
Node parent = callNode.getParent();
if (functionCall.tweakFunc.isGetterFunction()) {
Node newValue;
if (isRegistered) {
newValue = tweakInfo.getDefaultValueNode().cloneNode();
} else {
// When we find a getter of an unregistered tweak, there has
// already been a warning about it, so now just use a default
// value when stripping.
TweakFunction registerFunction =
functionCall.tweakFunc.registerFunction;
newValue = registerFunction.createDefaultValueNode();
}
parent.replaceChild(callNode, newValue);
} else {
Node voidZeroNode = IR.voidNode(IR.number(0).srcref(callNode))
.srcref(callNode);
parent.replaceChild(callNode, voidZeroNode);
}
}
}
return !tweakInfos.isEmpty();
}
/**
* Creates a JS object that holds a map of tweakId -> default value override.
*/
private Node createCompilerDefaultValueOverridesVarNode(
Node sourceInformationNode) {
Node objNode = IR.objectlit().srcref(sourceInformationNode);
for (Entry<String, Node> entry : compilerDefaultValueOverrides.entrySet()) {
Node objKeyNode = IR.stringKey(entry.getKey())
.copyInformationFrom(sourceInformationNode);
Node objValueNode = entry.getValue().cloneNode()
.copyInformationFrom(sourceInformationNode);
objKeyNode.addChildToBack(objValueNode);
objNode.addChildToBack(objKeyNode);
}
return objNode;
}
/** Sets the default values of tweaks based on compiler options. */
private void applyCompilerDefaultValueOverrides(
Map<String, TweakInfo> tweakInfos) {
for (Entry<String, Node> entry : compilerDefaultValueOverrides.entrySet()) {
String tweakId = entry.getKey();
TweakInfo tweakInfo = tweakInfos.get(tweakId);
if (tweakInfo == null) {
compiler.report(JSError.make(UNKNOWN_TWEAK_WARNING, tweakId));
} else {
TweakFunction registerFunc = tweakInfo.registerCall.tweakFunc;
Node value = entry.getValue();
if (!registerFunc.isValidNodeType(value.getType())) {
compiler.report(JSError.make(INVALID_TWEAK_DEFAULT_VALUE_WARNING,
tweakId, registerFunc.getName(),
registerFunc.getExpectedTypeName()));
} else {
tweakInfo.defaultValueNode = value;
}
}
}
}
/**
* Finds all calls to goog.tweak functions and emits warnings/errors if any
* of the calls have issues.
* @return A map of {@link TweakInfo} structures, keyed by tweak ID.
*/
private CollectTweaksResult collectTweaks(Node root) {
CollectTweaks pass = new CollectTweaks();
NodeTraversal.traverse(compiler, root, pass);
Map<String, TweakInfo> tweakInfos = pass.allTweaks;
for (TweakInfo tweakInfo : tweakInfos.values()) {
tweakInfo.emitAllWarnings();
}
return new CollectTweaksResult(tweakInfos, pass.getOverridesCalls);
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>_TWEAK_INIT_ERROR, tweakId));
break;
}
// Ensure tweak overrides occur before the tweak is registered.
if (tweakInfo.isRegistered()) {
compiler.report(
t.makeError(n, TWEAK_OVERRIDE_AFTER_REGISTERED_ERROR, tweakId));
break;
}
tweakDefaultValueNode = tweakIdNode.getNext();
tweakInfo.addOverrideDefaultValueCall(t.getSourceName(), tweakFunc, n,
tweakDefaultValueNode);
break;
case GET_BOOLEAN:
case GET_NUMBER:
case GET_STRING:
tweakInfo.addGetterCall(t.getSourceName(), tweakFunc, n);
}
}
}
/**
* Holds information about a call to a goog.tweak function.
*/
private static final class TweakFunctionCall {
final String sourceName;
final TweakFunction tweakFunc;
final Node callNode;
final Node valueNode;
TweakFunctionCall(String sourceName, TweakFunction tweakFunc,
Node callNode) {
this(sourceName, tweakFunc, callNode, null);
}
TweakFunctionCall(String sourceName, TweakFunction tweakFunc, Node callNode,
Node valueNode) {
this.sourceName = sourceName;
this.callNode = callNode;
this.tweakFunc = tweakFunc;
this.valueNode = valueNode;
}
Node getIdNode() {
return callNode.getFirstChild().getNext();
}
}
/**
* Stores information about a single tweak.
*/
private final class TweakInfo {
final String tweakId;
final List<TweakFunctionCall> functionCalls;
TweakFunctionCall registerCall;
Node defaultValueNode;
TweakInfo(String tweakId) {
this.tweakId = tweakId;
functionCalls = Lists.newArrayList();
}
/**
* If this tweak is registered, then looks for type warnings in default
* value parameters and getter functions. If it is not registered, emits an
* error for each function call.
*/
void emitAllWarnings() {
if (isRegistered()) {
emitAllTypeWarnings();
} else {
emitUnknownTweakErrors();
}
}
/**
* Emits a warning for each default value parameter that has the wrong type
* and for each getter function that was used for the wrong type of tweak.
*/
void emitAllTypeWarnings() {
for (TweakFunctionCall call : functionCalls) {
Node valueNode = call.valueNode;
TweakFunction tweakFunc = call.tweakFunc;
TweakFunction registerFunc = registerCall.tweakFunc;
if (valueNode != null) {
// For register* and overrideDefaultValue calls, ensure the default
// value is a literal of the correct type.
if (!registerFunc.isValidNodeType(valueNode.getType())) {
compiler.report(JSError.make(call.sourceName,
valueNode, INVALID_TWEAK_DEFAULT_VALUE_WARNING,
tweakId, registerFunc.getName(),
registerFunc.getExpectedTypeName()));
}
} else if (tweakFunc.isGetterFunction()) {
// For getter calls, ensure the correct getter was used.
if (!tweakFunc.isCorrectRegisterFunction(registerFunc)) {
compiler
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> name for this infimum. It's equivalent to "elements of this enum that
* are numbers".
*
* The best we can do is make up a new type. This is similar to what
* we do in UnionType#meet, which kind-of-sort-of makes sense, because
* an EnumElementType is a union of instances of a type.
*/
JSType meet(JSType that) {
JSType meetPrimitive = primitiveType.getGreatestSubtype(that);
if (meetPrimitive.isEmptyType()) {
return null;
} else {
return new EnumElementType(registry, meetPrimitive, name);
}
}
@Override
JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) {
primitiveType = primitiveType.resolve(t, scope);
primitiveObjectType = ObjectType.cast(primitiveType);
return this;
}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>(nicksantos): Organizationally, this feels like it should be in Rhino.
* But it depends on some coding convention stuff that's really part
* of JSCompiler.
*
* @author nicksantos@google.com (Nick Santos)
* @author pascallouis@google.com (Pascal-Louis Perez)
*/
final class FunctionTypeBuilder {
private final String fnName;
private final AbstractCompiler compiler;
private final CodingConvention codingConvention;
private final JSTypeRegistry typeRegistry;
private final Node errorRoot;
private final String sourceName;
private final Scope scope;
private FunctionContents contents = UnknownFunctionContents.get();
private JSType returnType = null;
private boolean returnTypeInferred = false;
private List<ObjectType> implementedInterfaces = null;
private List<ObjectType> extendedInterfaces = null;
private ObjectType baseType = null;
private JSType thisType = null;
private boolean isConstructor = false;
private boolean makesStructs = false;
private boolean makesDicts = false;
private boolean isInterface = false;
private Node parametersNode = null;
private ImmutableList<TemplateType> templateTypeNames = ImmutableList.of();
// TODO(johnlenz): verify we want both template and class template lists instead of a unified
// list.
private ImmutableList<TemplateType> classTemplateTypeNames = ImmutableList.of();
static final DiagnosticType EXTENDS_WITHOUT_TYPEDEF = DiagnosticType.warning(
"JSC_EXTENDS_WITHOUT_TYPEDEF",
"@extends used without @constructor or @interface for {0}");
static final DiagnosticType EXTENDS_NON_OBJECT = DiagnosticType.warning(
"JSC_EXTENDS_NON_OBJECT",
"{0} @extends non-object type {1}");
static final DiagnosticType RESOLVED_TAG_EMPTY = DiagnosticType.warning(
"JSC_RESOLVED_TAG_EMPTY",
"Could not resolve type in {0} tag of {1}");
static final DiagnosticType IMPLEMENTS_WITHOUT_CONSTRUCTOR =
DiagnosticType.warning(
"JSC_IMPLEMENTS_WITHOUT_CONSTRUCTOR",
"@implements used without @constructor or @interface for {0}");
static final DiagnosticType CONSTRUCTOR_REQUIRED =
DiagnosticType.warning("JSC_CONSTRUCTOR_REQUIRED",
"{0} used without @constructor for {1}");
static final DiagnosticType VAR_ARGS_MUST_BE_LAST = DiagnosticType.warning(
"JSC_VAR_ARGS_MUST_BE_LAST",
"variable length argument must be last");
static final DiagnosticType OPTIONAL_ARG_AT_END = DiagnosticType.warning(
"JSC_OPTIONAL_ARG_AT_END",
"optional arguments must be at the end");
static final DiagnosticType INEXISTANT_PARAM = DiagnosticType.warning(
"JSC_INEXISTANT_PARAM",
"parameter {0} does not appear in {1}''s parameter list");
static final DiagnosticType TYPE_REDEFINITION = DiagnosticType.warning(
"JSC_TYPE_REDEFINITION",
"attempted re-definition of type {0}\n"
+ "found : {1}\n"
+ "expected: {2}");
static final DiagnosticType TEMPLATE_TYPE_DUPLIC
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>ATED = DiagnosticType.warning(
"JSC_TEMPLATE_TYPE_DUPLICATED",
"Only one parameter type must be the template type");
static final DiagnosticType TEMPLATE_TYPE_EXPECTED = DiagnosticType.warning(
"JSC_TEMPLATE_TYPE_EXPECTED",
"The template type must be a parameter type");
static final DiagnosticType THIS_TYPE_NON_OBJECT =
DiagnosticType.warning(
"JSC_THIS_TYPE_NON_OBJECT",
"@this type of a function must be an object\n" +
"Actual type: {0}");
static final DiagnosticType SAME_INTERFACE_MULTIPLE_IMPLEMENTS =
DiagnosticType.warning(
"JSC_SAME_INTERFACE_MULTIPLE_IMPLEMENTS",
"Cannot @implement the same interface more than once\n" +
"Repeated interface: {0}");
private class ExtendedTypeValidator implements Predicate<JSType> {
@Override
public boolean apply(JSType type) {
ObjectType objectType = ObjectType.cast(type);
if (objectType == null) {
reportWarning(EXTENDS_NON_OBJECT, formatFnName(), type.toString());
return false;
} else if (objectType.isEmptyType()) {
reportWarning(RESOLVED_TAG_EMPTY, "@extends", formatFnName());
return false;
} else if (objectType.isUnknownType()) {
if (hasMoreTagsToResolve(objectType)) {
return true;
} else {
reportWarning(RESOLVED_TAG_EMPTY, "@extends", fnName);
return false;
}
} else {
return true;
}
}
}
private class ImplementedTypeValidator implements Predicate<JSType> {
@Override
public boolean apply(JSType type) {
ObjectType objectType = ObjectType.cast(type);
if (objectType == null) {
reportError(BAD_IMPLEMENTED_TYPE, fnName);
return false;
} else if (objectType.isEmptyType()) {
reportWarning(RESOLVED_TAG_EMPTY, "@implements", fnName);
return false;
} else if (objectType.isUnknownType()) {
if (hasMoreTagsToResolve(objectType)) {
return true;
} else {
reportWarning(RESOLVED_TAG_EMPTY, "@implements", fnName);
return false;
}
} else {
return true;
}
}
}
/**
* @param fnName The function name.
* @param compiler The compiler.
* @param errorRoot The node to associate with any warning generated by
* this builder.
* @param sourceName A source name for associating any warnings that
* we have to emit.
* @param scope The syntactic scope.
*/
FunctionTypeBuilder(String fnName, AbstractCompiler compiler,
Node errorRoot, String sourceName, Scope scope) {
Preconditions.checkNotNull(errorRoot);
this.fnName = fnName == null ? "" : fnName;
this.codingConvention = compiler.getCodingConvention();
this.typeRegistry = compiler.getTypeRegistry();
this.errorRoot = errorRoot;
this.sourceName = sourceName;
this.compiler = compiler;
this.scope = scope;
}
/** Format the function name for use in warnings. */
String formatFnName
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>() {
return fnName.isEmpty() ? "<anonymous>" : fnName;
}
/**
* Sets the contents of this function.
*/
FunctionTypeBuilder setContents(@Nullable FunctionContents contents) {
if (contents != null) {
this.contents = contents;
}
return this;
}
/**
* Infer the parameter and return types of a function from
* the parameter and return types of the function it is overriding.
*
* @param oldType The function being overridden. Does nothing if this is null.
* @param paramsParent The LP node of the function that we're assigning to.
* If null, that just means we're not initializing this to a function
* literal.
*/
FunctionTypeBuilder inferFromOverriddenFunction(
@Nullable FunctionType oldType, @Nullable Node paramsParent) {
if (oldType == null) {
return this;
}
returnType = oldType.getReturnType();
returnTypeInferred = oldType.isReturnTypeInferred();
if (paramsParent == null) {
// Not a function literal.
parametersNode = oldType.getParametersNode();
if (parametersNode == null) {
parametersNode = new FunctionParamBuilder(typeRegistry).build();
}
} else {
// We're overriding with a function literal. Apply type information
// to each parameter of the literal.
FunctionParamBuilder paramBuilder =
new FunctionParamBuilder(typeRegistry);
Iterator<Node> oldParams = oldType.getParameters().iterator();
boolean warnedAboutArgList = false;
boolean oldParamsListHitOptArgs = false;
for (Node currentParam = paramsParent.getFirstChild();
currentParam != null; currentParam = currentParam.getNext()) {
if (oldParams.hasNext()) {
Node oldParam = oldParams.next();
Node newParam = paramBuilder.newParameterFromNode(oldParam);
oldParamsListHitOptArgs = oldParamsListHitOptArgs ||
oldParam.isVarArgs() ||
oldParam.isOptionalArg();
// The subclass method might write its var_args as individual
// arguments.
if (currentParam.getNext() != null && newParam.isVarArgs()) {
newParam.setVarArgs(false);
newParam.setOptionalArg(true);
}
} else {
warnedAboutArgList |= addParameter(
paramBuilder,
typeRegistry.getNativeType(UNKNOWN_TYPE),
warnedAboutArgList,
codingConvention.isOptionalParameter(currentParam) ||
oldParamsListHitOptArgs,
codingConvention.isVarArgsParameter(currentParam));
}
}
// Clone any remaining params that aren't in the function literal,
// but make them optional.
while (oldParams.hasNext()) {
paramBuilder.newOptionalParameterFromNode(oldParams.next());
}
parametersNode = paramBuilder.build();
}
return this;
}
/**
* Infer the return type from JSDocInfo.
*/
FunctionTypeBuilder inferReturnType(@Nullable JSDocInfo info) {
if (info != null && info.hasReturnType()) {
returnType = info.getReturnType().evaluate(scope, typeRegistry);
returnTypeInferred = false;
}
return this;
}
/**
* Infer the role
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> of the function (whether it's a constructor or interface)
* and what it inherits from in JSDocInfo.
*/
FunctionTypeBuilder inferInheritance(@Nullable JSDocInfo info) {
if (info != null) {
isConstructor = info.isConstructor();
makesStructs = info.makesStructs();
makesDicts = info.makesDicts();
isInterface = info.isInterface();
if (makesStructs && !isConstructor) {
reportWarning(CONSTRUCTOR_REQUIRED, "@struct", formatFnName());
} else if (makesDicts && !isConstructor) {
reportWarning(CONSTRUCTOR_REQUIRED, "@dict", formatFnName());
}
// Class template types, which can be used in the scope of a constructor
// definition.
ImmutableList<String> typeParameters = info.getTemplateTypeNames();
if (!typeParameters.isEmpty()) {
if (isConstructor || isInterface) {
ImmutableList.Builder<TemplateType> builder = ImmutableList.builder();
for (String typeParameter : typeParameters) {
builder.add(typeRegistry.createTemplateType(typeParameter));
}
classTemplateTypeNames = builder.build();
typeRegistry.setTemplateTypeNames(classTemplateTypeNames);
}
}
// base type
if (info.hasBaseType()) {
if (isConstructor) {
JSType maybeBaseType =
info.getBaseType().evaluate(scope, typeRegistry);
if (maybeBaseType != null &&
maybeBaseType.setValidator(new ExtendedTypeValidator())) {
baseType = (ObjectType) maybeBaseType;
}
} else {
reportWarning(EXTENDS_WITHOUT_TYPEDEF, formatFnName());
}
}
// Implemented interfaces (for constructors only).
if (info.getImplementedInterfaceCount() > 0) {
if (isConstructor) {
implementedInterfaces = Lists.newArrayList();
Set<JSType> baseInterfaces = new HashSet<JSType>();
for (JSTypeExpression t : info.getImplementedInterfaces()) {
JSType maybeInterType = t.evaluate(scope, typeRegistry);
if (maybeInterType != null &&
maybeInterType.setValidator(new ImplementedTypeValidator())) {
// Disallow implementing the same base (not templatized) interface
// type more than once.
JSType baseInterface = maybeInterType;
if (baseInterface.toMaybeTemplatizedType() != null) {
baseInterface =
baseInterface.toMaybeTemplatizedType().getReferencedType();
}
if (baseInterfaces.contains(baseInterface)) {
reportWarning(SAME_INTERFACE_MULTIPLE_IMPLEMENTS,
baseInterface.toString());
} else {
baseInterfaces.add(baseInterface);
}
implementedInterfaces.add((ObjectType) maybeInterType);
}
}
} else if (isInterface) {
reportWarning(
TypeCheck.CONFLICTING_IMPLEMENTED_TYPE, formatFnName());
} else {
reportWarning(CONSTRUCTOR_REQUIRED, "@implements", formatFnName());
}
}
// extended interfaces (for interfaces only)
// We've already emitted a warning if this is not an interface.
if (isInterface) {
extendedInterfaces = Lists.newArrayList();
for (JSTypeExpression t : info.getExtendedInterfaces()) {
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>
JSType maybeInterfaceType = t.evaluate(scope, typeRegistry);
if (maybeInterfaceType != null &&
maybeInterfaceType.setValidator(new ExtendedTypeValidator())) {
extendedInterfaces.add((ObjectType) maybeInterfaceType);
}
}
}
}
return this;
}
/**
* Infers the type of {@code this}.
* @param type The type of this if the info is missing.
*/
FunctionTypeBuilder inferThisType(JSDocInfo info, JSType type) {
// Look at the @this annotation first.
inferThisType(info);
if (thisType == null) {
ObjectType objType = ObjectType.cast(type);
if (objType != null && (info == null || !info.hasType())) {
thisType = objType;
}
}
return this;
}
/**
* Infers the type of {@code this}.
* @param info The JSDocInfo for this function.
*/
FunctionTypeBuilder inferThisType(JSDocInfo info) {
JSType maybeThisType = null;
if (info != null && info.hasThisType()) {
// TODO(johnlenz): In ES5 strict mode a function can have a null or
// undefined "this" value, but all the existing "@this" annotations
// don't declare restricted types.
maybeThisType = info.getThisType().evaluate(scope, typeRegistry)
.restrictByNotNullOrUndefined();
}
if (maybeThisType != null) {
thisType = maybeThisType;
}
return this;
}
/**
* Infer the parameter types from the doc info alone.
*/
FunctionTypeBuilder inferParameterTypes(JSDocInfo info) {
// Create a fake args parent.
Node lp = IR.paramList();
for (String name : info.getParameterNames()) {
lp.addChildToBack(IR.name(name));
}
return inferParameterTypes(lp, info);
}
/**
* Infer the parameter types from the list of argument names and
* the doc info.
*/
FunctionTypeBuilder inferParameterTypes(@Nullable Node argsParent,
@Nullable JSDocInfo info) {
if (argsParent == null) {
if (info == null) {
return this;
} else {
return inferParameterTypes(info);
}
}
// arguments
Node oldParameterType = null;
if (parametersNode != null) {
oldParameterType = parametersNode.getFirstChild();
}
FunctionParamBuilder builder = new FunctionParamBuilder(typeRegistry);
boolean warnedAboutArgList = false;
Set<String> allJsDocParams = (info == null) ?
Sets.<String>newHashSet() :
Sets.newHashSet(info.getParameterNames());
boolean isVarArgs = false;
for (Node arg : argsParent.children()) {
String argumentName = arg.getString();
allJsDocParams.remove(argumentName);
// type from JSDocInfo
JSType parameterType = null;
boolean isOptionalParam = isOptionalParameter(arg, info);
isVarArgs = isVarArgsParameter(arg, info);
if (info != null && info.hasParameterType(argumentName)) {
parameterType =
info.
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>getParameterType(argumentName).evaluate(scope, typeRegistry);
} else if (arg.getJSDocInfo() != null && arg.getJSDocInfo().hasType()) {
parameterType =
arg.getJSDocInfo().getType().evaluate(scope, typeRegistry);
} else if (oldParameterType != null &&
oldParameterType.getJSType() != null) {
parameterType = oldParameterType.getJSType();
isOptionalParam = oldParameterType.isOptionalArg();
isVarArgs = oldParameterType.isVarArgs();
} else {
parameterType = typeRegistry.getNativeType(UNKNOWN_TYPE);
}
warnedAboutArgList |= addParameter(
builder, parameterType, warnedAboutArgList,
isOptionalParam,
isVarArgs);
if (oldParameterType != null) {
oldParameterType = oldParameterType.getNext();
}
}
// Copy over any old parameters that aren't in the param list.
if (!isVarArgs) {
while (oldParameterType != null && !isVarArgs) {
builder.newParameterFromNode(oldParameterType);
oldParameterType = oldParameterType.getNext();
}
}
for (String inexistentName : allJsDocParams) {
reportWarning(INEXISTANT_PARAM, inexistentName, formatFnName());
}
parametersNode = builder.build();
return this;
}
/**
* @return Whether the given param is an optional param.
*/
private boolean isOptionalParameter(
Node param, @Nullable JSDocInfo info) {
if (codingConvention.isOptionalParameter(param)) {
return true;
}
String paramName = param.getString();
return info != null && info.hasParameterType(paramName) &&
info.getParameterType(paramName).isOptionalArg();
}
/**
* Determine whether this is a var args parameter.
* @return Whether the given param is a var args param.
*/
private boolean isVarArgsParameter(
Node param, @Nullable JSDocInfo info) {
if (codingConvention.isVarArgsParameter(param)) {
return true;
}
String paramName = param.getString();
return info != null && info.hasParameterType(paramName) &&
info.getParameterType(paramName).isVarArgs();
}
/**
* Infer the template type from the doc info.
*/
FunctionTypeBuilder inferTemplateTypeName(
@Nullable JSDocInfo info, JSType ownerType) {
if (info != null && !info.getTemplateTypeNames().isEmpty()) {
ImmutableList.Builder<TemplateType> builder = ImmutableList.builder();
for (String key : info.getTemplateTypeNames()) {
builder.add(typeRegistry.createTemplateType(key));
}
templateTypeNames = builder.build();
} else {
templateTypeNames = ImmutableList.of();
}
ImmutableList<TemplateType> keys = templateTypeNames;
if (ownerType != null) {
ImmutableList<TemplateType> ownerTypeKeys =
ownerType.getTemplateTypeMap().getTemplateKeys();
if (!ownerTypeKeys.isEmpty()) {
ImmutableList.Builder<TemplateType> builder = ImmutableList.builder();
builder.addAll(templateTypeNames);
builder.addAll(ownerTypeKeys);
keys = builder
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>.build();
}
}
if (!keys.isEmpty()) {
typeRegistry.setTemplateTypeNames(keys);
}
return this;
}
/**
* Add a parameter to the param list.
* @param builder A builder.
* @param paramType The parameter type.
* @param warnedAboutArgList Whether we've already warned about arg ordering
* issues (like if optional args appeared before required ones).
* @param isOptional Is this an optional parameter?
* @param isVarArgs Is this a var args parameter?
* @return Whether a warning was emitted.
*/
private boolean addParameter(FunctionParamBuilder builder,
JSType paramType, boolean warnedAboutArgList,
boolean isOptional, boolean isVarArgs) {
boolean emittedWarning = false;
if (isOptional) {
// Remembering that an optional parameter has been encountered
// so that if a non optional param is encountered later, an
// error can be reported.
if (!builder.addOptionalParams(paramType) && !warnedAboutArgList) {
reportWarning(VAR_ARGS_MUST_BE_LAST);
emittedWarning = true;
}
} else if (isVarArgs) {
if (!builder.addVarArgs(paramType) && !warnedAboutArgList) {
reportWarning(VAR_ARGS_MUST_BE_LAST);
emittedWarning = true;
}
} else {
if (!builder.addRequiredParams(paramType) && !warnedAboutArgList) {
// An optional parameter was seen and this argument is not an optional
// or var arg so it is an error.
if (builder.hasVarArgs()) {
reportWarning(VAR_ARGS_MUST_BE_LAST);
} else {
reportWarning(OPTIONAL_ARG_AT_END);
}
emittedWarning = true;
}
}
return emittedWarning;
}
/**
* Builds the function type, and puts it in the registry.
*/
FunctionType buildAndRegister() {
if (returnType == null) {
// Infer return types.
// We need to be extremely conservative about this, because of two
// competing needs.
// 1) If we infer the return type of f too widely, then we won't be able
// to assign f to other functions.
// 2) If we infer the return type of f too narrowly, then we won't be
// able to override f in subclasses.
// So we only infer in cases where the user doesn't expect to write
// @return annotations--when it's very obvious that the function returns
// nothing.
if (!contents.mayHaveNonEmptyReturns() &&
!contents.mayHaveSingleThrow() &&
!contents.mayBeFromExterns()) {
returnType = typeRegistry.getNativeType(VOID_TYPE);
returnTypeInferred = true;
}
}
if (returnType == null) {
returnType = typeRegistry.getNativeType(UNKNOWN_TYPE);
}
if (parametersNode == null) {
throw new IllegalStateException(
"All Function types must have params and a return type");
}
FunctionType fnType;
if (isConstructor) {
fnType = getOrCreateConstructor();
} else if (isInterface)
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> {
fnType = typeRegistry.createInterfaceType(
fnName, contents.getSourceNode(), classTemplateTypeNames);
if (getScopeDeclaredIn().isGlobal() && !fnName.isEmpty()) {
typeRegistry.declareType(fnName, fnType.getInstanceType());
}
maybeSetBaseType(fnType);
} else {
fnType = new FunctionBuilder(typeRegistry)
.withName(fnName)
.withSourceNode(contents.getSourceNode())
.withParamsNode(parametersNode)
.withReturnType(returnType, returnTypeInferred)
.withTypeOfThis(thisType)
.withTemplateKeys(templateTypeNames)
.build();
maybeSetBaseType(fnType);
}
if (implementedInterfaces != null) {
fnType.setImplementedInterfaces(implementedInterfaces);
}
if (extendedInterfaces != null) {
fnType.setExtendedInterfaces(extendedInterfaces);
}
typeRegistry.clearTemplateTypeNames();
return fnType;
}
private void maybeSetBaseType(FunctionType fnType) {
if (!fnType.isInterface() && baseType != null) {
fnType.setPrototypeBasedOn(baseType);
fnType.extendTemplateTypeMapBasedOn(baseType);
}
}
/**
* Returns a constructor function either by returning it from the
* registry if it exists or creating and registering a new type. If
* there is already a type, then warn if the existing type is
* different than the one we are creating, though still return the
* existing function if possible. The primary purpose of this is
* that registering a constructor will fail for all built-in types
* that are initialized in {@link JSTypeRegistry}. We a) want to
* make sure that the type information specified in the externs file
* matches what is in the registry and b) annotate the externs with
* the {@link JSType} from the registry so that there are not two
* separate JSType objects for one type.
*/
private FunctionType getOrCreateConstructor() {
FunctionType fnType = typeRegistry.createConstructorType(
fnName, contents.getSourceNode(), parametersNode, returnType,
classTemplateTypeNames);
JSType existingType = typeRegistry.getType(fnName);
if (makesStructs) {
fnType.setStruct();
} else if (makesDicts) {
fnType.setDict();
}
if (existingType != null) {
boolean isInstanceObject = existingType.isInstanceType();
if (isInstanceObject || fnName.equals("Function")) {
FunctionType existingFn =
isInstanceObject ?
existingType.toObjectType().getConstructor() :
typeRegistry.getNativeFunctionType(FUNCTION_FUNCTION_TYPE);
if (existingFn.getSource() == null) {
existingFn.setSource(contents.getSourceNode());
}
if (!existingFn.hasEqualCallType(fnType)) {
reportWarning(TYPE_REDEFINITION, formatFnName(),
fnType.toString(), existingFn.toString());
}
return existingFn;
} else {
// We fall through and return the created type, even though it will fail
// to register. We have no choice as we have to return a function. We
// issue an error elsewhere though, so the
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> user should fix it.
}
}
maybeSetBaseType(fnType);
if (getScopeDeclaredIn().isGlobal() && !fnName.isEmpty()) {
typeRegistry.declareType(fnName, fnType.getInstanceType());
}
return fnType;
}
private void reportWarning(DiagnosticType warning, String ... args) {
compiler.report(JSError.make(sourceName, errorRoot, warning, args));
}
private void reportError(DiagnosticType error, String ... args) {
compiler.report(JSError.make(sourceName, errorRoot, error, args));
}
/**
* Determines whether the given JsDoc info declares a function type.
*/
static boolean isFunctionTypeDeclaration(JSDocInfo info) {
return info.getParameterCount() > 0 ||
info.hasReturnType() ||
info.hasThisType() ||
info.isConstructor() ||
info.isInterface();
}
/**
* The scope that we should declare this function in, if it needs
* to be declared in a scope. Notice that TypedScopeCreator takes
* care of most scope-declaring.
*/
private Scope getScopeDeclaredIn() {
int dotIndex = fnName.indexOf(".");
if (dotIndex != -1) {
String rootVarName = fnName.substring(0, dotIndex);
Var rootVar = scope.getVar(rootVarName);
if (rootVar != null) {
return rootVar.getScope();
}
}
return scope;
}
/**
* Check whether a type is resolvable in the future
* If this has a supertype that hasn't been resolved yet, then we can assume
* this type will be OK once the super type resolves.
* @param objectType
* @return true if objectType is resolvable in the future
*/
private static boolean hasMoreTagsToResolve(ObjectType objectType) {
Preconditions.checkArgument(objectType.isUnknownType());
if (objectType.getImplicitPrototype() != null) {
// constructor extends class
if (objectType.getImplicitPrototype().isResolved()) {
return false;
} else {
return true;
}
} else {
// interface extends interfaces
FunctionType ctor = objectType.getConstructor();
if (ctor != null) {
for (ObjectType interfaceType : ctor.getExtendedInterfaces()) {
if (!interfaceType.isResolved()) {
return true;
}
}
}
return false;
}
}
/** Holds data dynamically inferred about functions. */
static interface FunctionContents {
/** Returns the source node of this function. May be null. */
Node getSourceNode();
/** Returns if the function may be in externs. */
boolean mayBeFromExterns();
/** Returns if a return of a real value (not undefined) appears. */
boolean mayHaveNonEmptyReturns();
/** Returns if this consists of a single throw. */
boolean mayHaveSingleThrow();
/** Gets a list of variables in this scope that are escaped. */
Iterable<String> getEscapedVarNames();
/** Gets a list of variables whose properties are escaped. */
Set<String> getEscapedQualifiedNames();
/** Gets the number of times each variable has been assigned. */
Multiset
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>addAll(
convention.getIndirectlyDeclaredProperties());
for (Name name : namespace.getNameForest()) {
// Skip extern names. Externs are often not runnable as real code,
// and will do things like:
// var x;
// x.method;
// which this check forbids.
if (name.inExterns) {
continue;
}
checkDescendantNames(name, name.globalSets + name.localSets > 0);
}
}
private void findPrototypeProps(String type, Set<String> props) {
Name slot = namespace.getSlot(type);
if (slot != null) {
for (Ref ref : slot.getRefs()) {
if (ref.type == Ref.Type.PROTOTYPE_GET) {
Node fullName = ref.getNode().getParent().getParent();
if (fullName.isGetProp()) {
props.add(fullName.getLastChild().getString());
}
}
}
}
}
/**
* Checks to make sure all the descendants of a name are defined if they
* are referenced.
*
* @param name A global name.
* @param nameIsDefined If true, {@code name} is defined. Otherwise, it's
* undefined, and any references to descendant names should emit warnings.
*/
private void checkDescendantNames(Name name, boolean nameIsDefined) {
if (name.props != null) {
for (Name prop : name.props) {
// if the ancestor of a property is not defined, then we should emit
// warnings for all references to the property.
boolean propIsDefined = false;
if (nameIsDefined) {
// if the ancestor of a property is defined, then let's check that
// the property is also explicitly defined if it needs to be.
propIsDefined = (!propertyMustBeInitializedByFullName(prop) ||
prop.globalSets + prop.localSets > 0);
}
validateName(prop, propIsDefined);
checkDescendantNames(prop, propIsDefined);
}
}
}
private void validateName(Name name, boolean isDefined) {
// If the name is not defined, emit warnings for each reference. While
// we're looking through each reference, check all the module dependencies.
Ref declaration = name.getDeclaration();
Name parent = name.parent;
JSModuleGraph moduleGraph = compiler.getModuleGraph();
for (Ref ref : name.getRefs()) {
// Don't worry about global exprs.
boolean isGlobalExpr = ref.getNode().getParent().isExprResult();
if (!isDefined && !isTypedef(ref)) {
if (!isGlobalExpr) {
reportRefToUndefinedName(name, ref);
}
} else if (declaration != null &&
ref.getModule() != declaration.getModule() &&
!moduleGraph.dependsOn(
ref.getModule(), declaration.getModule())) {
reportBadModuleReference(name, ref);
} else {
// Check for late references.
if (ref.scope.isGlobal()) {
// Prototype references are special, because in our reference graph,
// A.prototype counts as a reference to A.
boolean isPrototypeGet = (ref.type == Ref.Type.PROTOTYPE_
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp.type;
import com.google.javascript.jscomp.graph.LatticeElement;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.StaticScope;
import com.google.javascript.rhino.jstype.StaticSlot;
/**
* A symbol table for inferring types during data flow analysis.
*
* Each flow scope represents the types of all variables in the scope at
* a particular point in the flow analysis.
*
* @author nicksantos@google.com (Nick Santos)
*/
public interface FlowScope extends StaticScope<JSType>, LatticeElement {
/**
* Creates a child of this flow scope, to represent an instruction
* directly following this one.
*/
FlowScope createChildFlowScope();
/**
* Defines the type of a symbol at this point in the flow.
* @throws IllegalArgumentException If no slot for this symbol exists.
*/
void inferSlotType(String symbol, JSType type);
/**
* Infer the type of a qualified name.
*
* When traversing the control flow of a function, simple names are
* declared at the bottom of the flow lattice. But there are far too many
* qualified names to be able to do this and be performant. So the bottoms
* of qualified names are declared lazily.
*
* Therefore, when inferring a qualified slot, we need both the "bottom"
* type of the slot when we enter the scope, and the current type being
* inferred.
*/
void inferQualifiedSlot(Node node, String symbol, JSType bottomType,
JSType inferredType);
/**
* Optimize this scope and return a new FlowScope with faster lookup.
*/
FlowScope optimize();
/**
* Tries to find a unique refined variable in the refined scope, up to the
* the blind scope.
* @param blindScope The scope before the refinement, i.e. some parent of the
* this scope or itself.
* @return The unique refined variable if found or null.
*/
StaticSlot<JSType> findUniqueRefinedSlot(FlowScope blindScope);
/**
* Look through the given scope, and try to find slots where it doesn't
* have enough type information. Then fill in that type information
* with stuff that we've inferred in the local flow.
*/
void completeScope(StaticScope<JSType> scope);
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> // and return types of G.prototype.constructor.
childCtor.cloneWithoutArrowType(),
childCtor.getSource());
}
}
/**
* {@inheritDoc}
*
* <p>Understands several different inheritance patterns that occur in
* Google code (various uses of {@code inherits} and {@code mixin}).
*/
@Override
public SubclassRelationship getClassesDefinedByCall(Node callNode) {
SubclassRelationship relationship =
super.getClassesDefinedByCall(callNode);
if (relationship != null) {
return relationship;
}
Node callName = callNode.getFirstChild();
SubclassType type = typeofClassDefiningName(callName);
if (type != null) {
Node subclass = null;
Node superclass = callNode.getLastChild();
// There are six possible syntaxes for a class-defining method:
// SubClass.inherits(SuperClass)
// goog.inherits(SubClass, SuperClass)
// goog$inherits(SubClass, SuperClass)
// SubClass.mixin(SuperClass.prototype)
// goog.mixin(SubClass.prototype, SuperClass.prototype)
// goog$mixin(SubClass.prototype, SuperClass.prototype)
boolean isDeprecatedCall = callNode.getChildCount() == 2 &&
callName.isGetProp();
if (isDeprecatedCall) {
// SubClass.inherits(SuperClass)
subclass = callName.getFirstChild();
} else if (callNode.getChildCount() == 3) {
// goog.inherits(SubClass, SuperClass)
subclass = callName.getNext();
} else {
return null;
}
if (type == SubclassType.MIXIN) {
// Only consider mixins that mix two prototypes as related to
// inheritance.
if (!endsWithPrototype(superclass)) {
return null;
}
if (!isDeprecatedCall) {
if (!endsWithPrototype(subclass)) {
return null;
}
// Strip off the prototype from the name.
subclass = subclass.getFirstChild();
}
superclass = superclass.getFirstChild();
}
// bail out if either of the side of the "inherits"
// isn't a real class name. This prevents us from
// doing something weird in cases like:
// goog.inherits(MySubClass, cond ? SuperClass1 : BaseClass2)
if (subclass != null &&
subclass.isUnscopedQualifiedName() &&
superclass.isUnscopedQualifiedName()) {
return new SubclassRelationship(type, subclass, superclass);
}
}
return null;
}
/**
* Determines whether the given node is a class-defining name, like
* "inherits" or "mixin."
* @return The type of class-defining name, or null.
*/
private SubclassType typeofClassDefiningName(Node callName) {
// Check if the method name matches one of the class-defining methods.
String methodName = null;
if (callName.isGetProp()) {
methodName = callName.getLastChild().getString();
} else if (callName.isName()) {
String name = callName.getString();
int dollarIndex = name.lastIndexOf('$');
if (dollarIndex != -1) {
methodName = name.substring(
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>dollarIndex + 1);
}
}
if (methodName != null) {
if (methodName.equals("inherits")) {
return SubclassType.INHERITS;
} else if (methodName.equals("mixin")) {
return SubclassType.MIXIN;
}
}
return null;
}
@Override
public boolean isSuperClassReference(String propertyName) {
return "superClass_".equals(propertyName) ||
super.isSuperClassReference(propertyName);
}
/**
* Given a qualified name node, returns whether "prototype" is at the end.
* For example:
* a.b.c => false
* a.b.c.prototype => true
*/
private boolean endsWithPrototype(Node qualifiedName) {
return qualifiedName.isGetProp() &&
qualifiedName.getLastChild().getString().equals("prototype");
}
/**
* Extracts X from goog.provide('X'), if the applied Node is goog.
*
* @return The extracted class name, or null.
*/
@Override
public String extractClassNameIfProvide(Node node, Node parent){
return extractClassNameIfGoog(node, parent, "goog.provide");
}
/**
* Extracts X from goog.require('X'), if the applied Node is goog.
*
* @return The extracted class name, or null.
*/
@Override
public String extractClassNameIfRequire(Node node, Node parent){
return extractClassNameIfGoog(node, parent, "goog.require");
}
private static String extractClassNameIfGoog(Node node, Node parent,
String functionName){
String className = null;
if (NodeUtil.isExprCall(parent)) {
Node callee = node.getFirstChild();
if (callee != null && callee.isGetProp()) {
String qualifiedName = callee.getQualifiedName();
if (functionName.equals(qualifiedName)) {
Node target = callee.getNext();
if (target != null && target.isString()) {
className = target.getString();
}
}
}
}
return className;
}
/**
* Use closure's implementation.
* @return closure's function name for exporting properties.
*/
@Override
public String getExportPropertyFunction() {
return "goog.exportProperty";
}
/**
* Use closure's implementation.
* @return closure's function name for exporting symbols.
*/
@Override
public String getExportSymbolFunction() {
return "goog.exportSymbol";
}
@Override
public List<String> identifyTypeDeclarationCall(Node n) {
Node callName = n.getFirstChild();
if ("goog.addDependency".equals(callName.getQualifiedName()) &&
n.getChildCount() >= 3) {
Node typeArray = callName.getNext().getNext();
if (typeArray.isArrayLit()) {
List<String> typeNames = Lists.newArrayList();
for (Node name = typeArray.getFirstChild(); name != null;
name = name.getNext()) {
if (name.isString()) {
typeNames.add(name.getString());
}
}
return typeNames;
}
}
return super.identifyTypeDeclarationCall(n);
}
@Override
public String
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>_INSTANCE_TYPE),
new AssertionFunctionSpec("goog.asserts.assertObject",
JSTypeNative.OBJECT_TYPE),
new AssertionFunctionSpec("goog.asserts.assertArray",
JSTypeNative.ARRAY_TYPE),
new AssertInstanceofSpec("goog.asserts.assertInstanceof")
);
}
@Override
public Bind describeFunctionBind(Node n, boolean useTypeInfo) {
Bind result = super.describeFunctionBind(n, useTypeInfo);
if (result != null) {
return result;
}
if (!n.isCall()) {
return null;
}
Node callTarget = n.getFirstChild();
String name = callTarget.getQualifiedName();
if (name != null) {
if (name.equals("goog.bind")
|| name.equals("goog$bind")) {
// goog.bind(fn, self, args...);
Node fn = callTarget.getNext();
if (fn == null) {
return null;
}
Node thisValue = safeNext(fn);
Node parameters = safeNext(thisValue);
return new Bind(fn, thisValue, parameters);
}
if (name.equals("goog.partial") || name.equals("goog$partial")) {
// goog.partial(fn, args...);
Node fn = callTarget.getNext();
if (fn == null) {
return null;
}
Node thisValue = null;
Node parameters = safeNext(fn);
return new Bind(fn, thisValue, parameters);
}
}
return null;
}
@Override
public Collection<String> getIndirectlyDeclaredProperties() {
return indirectlyDeclaredProperties;
}
private Node safeNext(Node n) {
if (n != null) {
return n.getNext();
}
return null;
}
/**
* A function that will throw an exception when if the value is not
* an instanceof a specific type.
*/
public static class AssertInstanceofSpec extends AssertionFunctionSpec {
public AssertInstanceofSpec(String functionName) {
super(functionName, JSTypeNative.OBJECT_TYPE);
}
/**
* Returns the type for a type assertion, or null if the function asserts
* that the node must not be null or undefined.
*/
@Override
public JSType getAssertedType(Node call, JSTypeRegistry registry) {
if (call.getChildCount() > 2) {
Node constructor = call.getFirstChild().getNext().getNext();
if (constructor != null) {
JSType ownerType = constructor.getJSType();
if (ownerType != null
&& ownerType.isFunctionType()
&& ownerType.isConstructor()) {
FunctionType functionType = ((FunctionType) ownerType);
return functionType.getInstanceType();
}
}
}
return super.getAssertedType(call, registry);
}
}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> If the property is not defined, this was not a quoted key. The
* QUOTED_PROP int property is only assigned to STRING tokens used as
* object lit keys.
* @return true if this was a quoted string key in an object literal.
*/
@Override
public boolean isQuotedString() {
return getBooleanProp(QUOTED_PROP);
}
/**
* This should only be called for STRING nodes created in object lits.
*/
@Override
public void setQuotedString() {
putBooleanProp(QUOTED_PROP, true);
}
private String str;
}
// PropListItems must be immutable so that they can be shared.
private interface PropListItem {
int getType();
PropListItem getNext();
PropListItem chain(PropListItem next);
Object getObjectValue();
int getIntValue();
}
private abstract static class AbstractPropListItem
implements PropListItem, Serializable {
private static final long serialVersionUID = 1L;
private final PropListItem next;
private final int propType;
AbstractPropListItem(int propType, PropListItem next) {
this.propType = propType;
this.next = next;
}
@Override
public int getType() {
return propType;
}
@Override
public PropListItem getNext() {
return next;
}
@Override
public abstract PropListItem chain(PropListItem next);
}
// A base class for Object storing props
private static class ObjectPropListItem
extends AbstractPropListItem {
private static final long serialVersionUID = 1L;
private final Object objectValue;
ObjectPropListItem(int propType, Object objectValue, PropListItem next) {
super(propType, next);
this.objectValue = objectValue;
}
@Override
public int getIntValue() {
throw new UnsupportedOperationException();
}
@Override
public Object getObjectValue() {
return objectValue;
}
@Override
public String toString() {
return objectValue == null ? "null" : objectValue.toString();
}
@Override
public PropListItem chain(PropListItem next) {
return new ObjectPropListItem(getType(), objectValue, next);
}
}
// A base class for int storing props
private static class IntPropListItem extends AbstractPropListItem {
private static final long serialVersionUID = 1L;
final int intValue;
IntPropListItem(int propType, int intValue, PropListItem next) {
super(propType, next);
this.intValue = intValue;
}
@Override
public int getIntValue() {
return intValue;
}
@Override
public Object getObjectValue() {
throw new UnsupportedOperationException();
}
@Override
public String toString() {
return String.valueOf(intValue);
}
@Override
public PropListItem chain(PropListItem next) {
return new IntPropListItem(getType(), intValue, next);
}
}
public Node(int nodeType) {
type = nodeType;
parent = null;
sourcePosition = -1;
}
public Node(int nodeType, Node child) {
Preconditions.checkArgument(child.parent == null,
"new child has existing parent");
Preconditions.checkArgument(child.next == null,
"new child has existing sibling");
type = nodeType
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> this(nodeType, left, mid, right);
sourcePosition = mergeLineCharNo(lineno, charno);
}
public Node(int nodeType, Node left, Node mid, Node mid2, Node right,
int lineno, int charno) {
this(nodeType, left, mid, mid2, right);
sourcePosition = mergeLineCharNo(lineno, charno);
}
public Node(int nodeType, Node[] children, int lineno, int charno) {
this(nodeType, children);
sourcePosition = mergeLineCharNo(lineno, charno);
}
public Node(int nodeType, Node[] children) {
this.type = nodeType;
parent = null;
if (children.length != 0) {
this.first = children[0];
this.last = children[children.length - 1];
for (int i = 1; i < children.length; i++) {
if (null != children[i - 1].next) {
// fail early on loops. implies same node in array twice
throw new IllegalArgumentException("duplicate child");
}
children[i - 1].next = children[i];
Preconditions.checkArgument(children[i - 1].parent == null);
children[i - 1].parent = this;
}
Preconditions.checkArgument(children[children.length - 1].parent == null);
children[children.length - 1].parent = this;
if (null != this.last.next) {
// fail early on loops. implies same node in array twice
throw new IllegalArgumentException("duplicate child");
}
}
}
public static Node newNumber(double number) {
return new NumberNode(number);
}
public static Node newNumber(double number, int lineno, int charno) {
return new NumberNode(number, lineno, charno);
}
public static Node newString(String str) {
return new StringNode(Token.STRING, str);
}
public static Node newString(int type, String str) {
return new StringNode(type, str);
}
public static Node newString(String str, int lineno, int charno) {
return new StringNode(Token.STRING, str, lineno, charno);
}
public static Node newString(int type, String str, int lineno, int charno) {
return new StringNode(type, str, lineno, charno);
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public boolean hasChildren() {
return first != null;
}
public Node getFirstChild() {
return first;
}
public Node getLastChild() {
return last;
}
public Node getNext() {
return next;
}
public Node getChildBefore(Node child) {
if (child == first) {
return null;
}
Node n = first;
while (n.next != child) {
n = n.next;
if (n == null) {
throw new RuntimeException("node is not a child");
}
}
return n;
}
public Node getChildAtIndex(int i) {
Node n = first;
while
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>();
if (node != null) {
Node oldNext = node.next;
node.next = children;
lastSibling.next = oldNext;
if (node == last) {
last = lastSibling;
}
} else {
// Append to the beginning.
if (first != null) {
lastSibling.next = first;
} else {
last = lastSibling;
}
first = children;
}
}
/**
* Detach a child from its parent and siblings.
*/
public void removeChild(Node child) {
Node prev = getChildBefore(child);
if (prev == null) {
first = first.next;
} else {
prev.next = child.next;
}
if (child == last) {
last = prev;
}
child.next = null;
child.parent = null;
}
/**
* Detaches child from Node and replaces it with newChild.
*/
public void replaceChild(Node child, Node newChild) {
Preconditions.checkArgument(newChild.next == null,
"The new child node has siblings.");
Preconditions.checkArgument(newChild.parent == null,
"The new child node already has a parent.");
// Copy over important information.
newChild.copyInformationFrom(child);
newChild.next = child.next;
newChild.parent = this;
if (child == first) {
first = newChild;
} else {
Node prev = getChildBefore(child);
prev.next = newChild;
}
if (child == last) {
last = newChild;
}
child.next = null;
child.parent = null;
}
public void replaceChildAfter(Node prevChild, Node newChild) {
Preconditions.checkArgument(prevChild.parent == this,
"prev is not a child of this node.");
Preconditions.checkArgument(newChild.next == null,
"The new child node has siblings.");
Preconditions.checkArgument(newChild.parent == null,
"The new child node already has a parent.");
// Copy over important information.
newChild.copyInformationFrom(prevChild);
Node child = prevChild.next;
newChild.next = child.next;
newChild.parent = this;
prevChild.next = newChild;
if (child == last) {
last = newChild;
}
child.next = null;
child.parent = null;
}
@VisibleForTesting
PropListItem lookupProperty(int propType) {
PropListItem x = propListHead;
while (x != null && propType != x.getType()) {
x = x.getNext();
}
return x;
}
/**
* Clone the properties from the provided node without copying
* the property object. The receiving node may not have any
* existing properties.
* @param other The node to clone properties from.
* @return this node.
*/
public Node clonePropsFrom(Node other) {
Preconditions.checkState(this.propListHead == null,
"Node has existing properties.");
this.propListHead = other.propListHead;
return this;
}
public void removeProp(int propType) {
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> PropListItem result = removeProp(propListHead, propType);
if (result != propListHead) {
propListHead = result;
}
}
/**
* @param item The item to inspect
* @param propType The property to look for
* @return The replacement list if the property was removed, or
* 'item' otherwise.
*/
private PropListItem removeProp(PropListItem item, int propType) {
if (item == null) {
return null;
} else if (item.getType() == propType) {
return item.getNext();
} else {
PropListItem result = removeProp(item.getNext(), propType);
if (result != item.getNext()) {
return item.chain(result);
} else {
return item;
}
}
}
public Object getProp(int propType) {
PropListItem item = lookupProperty(propType);
if (item == null) {
return null;
}
return item.getObjectValue();
}
public boolean getBooleanProp(int propType) {
return getIntProp(propType) != 0;
}
/**
* Returns the integer value for the property, or 0 if the property
* is not defined.
*/
public int getIntProp(int propType) {
PropListItem item = lookupProperty(propType);
if (item == null) {
return 0;
}
return item.getIntValue();
}
public int getExistingIntProp(int propType) {
PropListItem item = lookupProperty(propType);
if (item == null) {
throw new IllegalStateException("missing prop: " + propType);
}
return item.getIntValue();
}
public void putProp(int propType, Object value) {
removeProp(propType);
if (value != null) {
propListHead = createProp(propType, value, propListHead);
}
}
public void putBooleanProp(int propType, boolean value) {
putIntProp(propType, value ? 1 : 0);
}
public void putIntProp(int propType, int value) {
removeProp(propType);
if (value != 0) {
propListHead = createProp(propType, value, propListHead);
}
}
PropListItem createProp(int propType, Object value, PropListItem next) {
return new ObjectPropListItem(propType, value, next);
}
PropListItem createProp(int propType, int value, PropListItem next) {
return new IntPropListItem(propType, value, next);
}
// Gets all the property types, in sorted order.
private int[] getSortedPropTypes() {
int count = 0;
for (PropListItem x = propListHead; x != null; x = x.getNext()) {
count++;
}
int[] keys = new int[count];
for (PropListItem x = propListHead; x != null; x = x.getNext()) {
count--;
keys[count] = x.getType();
}
Arrays.sort(keys);
return keys;
}
/** Can only be called when <tt>getType() == TokenStream.NUMBER</tt> */
public double
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> getDouble() throws UnsupportedOperationException {
if (this.getType() == Token.NUMBER) {
throw new IllegalStateException(
"Number node not created with Node.newNumber");
} else {
throw new UnsupportedOperationException(this + " is not a number node");
}
}
/**
* Can only be called when <tt>getType() == Token.NUMBER</tt>
* @param value value to set.
*/
public void setDouble(double value) throws UnsupportedOperationException {
if (this.getType() == Token.NUMBER) {
throw new IllegalStateException(
"Number node not created with Node.newNumber");
} else {
throw new UnsupportedOperationException(this + " is not a string node");
}
}
/** Can only be called when node has String context. */
public String getString() throws UnsupportedOperationException {
if (this.getType() == Token.STRING) {
throw new IllegalStateException(
"String node not created with Node.newString");
} else {
throw new UnsupportedOperationException(this + " is not a string node");
}
}
/**
* Can only be called for a Token.STRING or Token.NAME.
* @param value the value to set.
*/
public void setString(String value) throws UnsupportedOperationException {
if (this.getType() == Token.STRING || this.getType() == Token.NAME) {
throw new IllegalStateException(
"String node not created with Node.newString");
} else {
throw new UnsupportedOperationException(this + " is not a string node");
}
}
@Override
public String toString() {
return toString(true, true, true);
}
public String toString(
boolean printSource,
boolean printAnnotations,
boolean printType) {
StringBuilder sb = new StringBuilder();
toString(sb, printSource, printAnnotations, printType);
return sb.toString();
}
private void toString(
StringBuilder sb,
boolean printSource,
boolean printAnnotations,
boolean printType) {
sb.append(Token.name(type));
if (this instanceof StringNode) {
sb.append(' ');
sb.append(getString());
} else if (type == Token.FUNCTION) {
sb.append(' ');
// In the case of JsDoc trees, the first child is often not a string
// which causes exceptions to be thrown when calling toString or
// toStringTree.
if (first == null || first.getType() != Token.NAME) {
sb.append("<invalid>");
} else {
sb.append(first.getString());
}
} else if (type == Token.NUMBER) {
sb.append(' ');
sb.append(getDouble());
}
if (printSource) {
int lineno = getLineno();
if (lineno != -1) {
sb.append(' ');
sb.append(lineno);
}
}
if (printAnnotations) {
int[] keys = getSortedPropTypes();
for (int i = 0; i < keys.length; i++) {
int type = keys[i];
PropListItem x = lookupProperty(type);
sb.append(" [");
sb.append(propToString(type));
sb.append(": ");
String value;
switch (type) {
default:
value = x.toString();
break;
}
sb.append
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>tree2: " + diff.nodeB.toStringTree();
}
return null;
}
/**
* Compare this node to node2 recursively and return the first pair of nodes
* that differs doing a preorder depth-first traversal. Package private for
* testing. Returns null if the nodes are equivalent.
*/
NodeMismatch checkTreeEqualsImpl(Node node2) {
if (!isEquivalentTo(node2, false, false, false)) {
return new NodeMismatch(this, node2);
}
NodeMismatch res = null;
Node n, n2;
for (n = first, n2 = node2.first;
res == null && n != null;
n = n.next, n2 = n2.next) {
if (node2 == null) {
throw new IllegalStateException();
}
res = n.checkTreeEqualsImpl(n2);
if (res != null) {
return res;
}
}
return res;
}
/**
* Compare this node to node2 recursively and return the first pair of nodes
* that differs doing a preorder depth-first traversal. Package private for
* testing. Returns null if the nodes are equivalent.
*/
NodeMismatch checkTreeTypeAwareEqualsImpl(Node node2) {
// Do a non-recursive equivalents check.
if (!isEquivalentTo(node2, true, false, false)) {
return new NodeMismatch(this, node2);
}
NodeMismatch res = null;
Node n, n2;
for (n = first, n2 = node2.first;
res == null && n != null;
n = n.next, n2 = n2.next) {
res = n.checkTreeTypeAwareEqualsImpl(n2);
if (res != null) {
return res;
}
}
return res;
}
/** Returns true if this node is equivalent semantically to another */
public boolean isEquivalentTo(Node node) {
return isEquivalentTo(node, false, true, false);
}
/** Checks equivalence without going into inner functions */
public boolean isEquivalentToShallow(Node node) {
return isEquivalentTo(node, false, true, true);
}
/**
* Returns true if this node is equivalent semantically to another and
* the types are equivalent.
*/
public boolean isEquivalentToTyped(Node node) {
return isEquivalentTo(node, true, true, false);
}
/**
* @param compareJsType Whether to compare the JSTypes of the nodes.
* @param recur Whether to compare the children of the current node, if
* not only the the count of the children are compared.
* @param shallow If true, the method doesn't recur into inner functions.
* @return Whether this node is equivalent semantically to the provided node.
*/
boolean isEquivalentTo(
Node node, boolean compareJsType, boolean recur, boolean shallow) {
if (type != node.getType()
|| getChildCount() != node.getChildCount()
|| this.getClass() != node.getClass()) {
return false;
}
if (compareJsType && !JSType.isEquivalent(jsType, node.getJSType())) {
return false;
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> if (type == Token.INC || type == Token.DEC) {
int post1 = this.getIntProp(INCRDECR_PROP);
int post2 = node.getIntProp(INCRDECR_PROP);
if (post1 != post2) {
return false;
}
} else if (type == Token.STRING || type == Token.STRING_KEY) {
if (type == Token.STRING_KEY) {
int quoted1 = this.getIntProp(QUOTED_PROP);
int quoted2 = node.getIntProp(QUOTED_PROP);
if (quoted1 != quoted2) {
return false;
}
}
int slashV1 = this.getIntProp(SLASH_V);
int slashV2 = node.getIntProp(SLASH_V);
if (slashV1 != slashV2) {
return false;
}
} else if (type == Token.CALL) {
if (this.getBooleanProp(FREE_CALL) != node.getBooleanProp(FREE_CALL)) {
return false;
}
}
if (recur) {
Node n, n2;
for (n = first, n2 = node.first;
n != null;
n = n.next, n2 = n2.next) {
if (!n.isEquivalentTo(
n2, compareJsType, !(shallow && n.isFunction()), shallow)) {
return false;
}
}
}
return true;
}
/**
* This function takes a set of GETPROP nodes and produces a string that is
* each property separated by dots. If the node ultimately under the left
* sub-tree is not a simple name, this is not a valid qualified name.
*
* @return a null if this is not a qualified name, or a dot-separated string
* of the name and properties.
*/
public String getQualifiedName() {
if (type == Token.NAME) {
String name = getString();
return name.isEmpty() ? null : name;
} else if (type == Token.GETPROP) {
String left = getFirstChild().getQualifiedName();
if (left == null) {
return null;
}
return left + "." + getLastChild().getString();
} else if (type == Token.THIS) {
return "this";
} else {
return null;
}
}
/**
* Returns whether a node corresponds to a simple or a qualified name, such as
* <code>x</code> or <code>a.b.c</code> or <code>this.a</code>.
*/
public boolean isQualifiedName() {
switch (getType()) {
case Token.NAME:
return getString().isEmpty() ? false : true;
case Token.THIS:
return true;
case Token.GETPROP:
return getFirstChild().isQualifiedName();
default:
return false;
}
}
/**
* Returns whether a node corresponds to a simple or a qualified name without
* a "this" reference, such as <code>a.b.c</code>, but not <code>this.a</code>
* .
*/
public boolean isUnscopedQualifiedName() {
switch (getType()) {
case Token.NAME:
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> or constructor call's side effect flags.
* This property is only meaningful for {@link Token#CALL} and
* {@link Token#NEW} nodes.
*/
public void setSideEffectFlags(int flags) {
Preconditions.checkArgument(
getType() == Token.CALL || getType() == Token.NEW,
"setIsNoSideEffectsCall only supports CALL and NEW nodes, got " +
Token.name(getType()));
putIntProp(SIDE_EFFECT_FLAGS, flags);
}
public void setSideEffectFlags(SideEffectFlags flags) {
setSideEffectFlags(flags.valueOf());
}
/**
* Returns the side effects flags for this node.
*/
public int getSideEffectFlags() {
return getIntProp(SIDE_EFFECT_FLAGS);
}
/**
* A helper class for getting and setting the side-effect flags.
* @author johnlenz@google.com (John Lenz)
*/
public static class SideEffectFlags {
private int value = Node.SIDE_EFFECTS_ALL;
public SideEffectFlags() {
}
public SideEffectFlags(int value) {
this.value = value;
}
public int valueOf() {
return value;
}
/** All side-effect occur and the returned results are non-local. */
public SideEffectFlags setAllFlags() {
value = Node.SIDE_EFFECTS_ALL;
return this;
}
/** No side-effects occur and the returned results are local. */
public SideEffectFlags clearAllFlags() {
value = Node.NO_SIDE_EFFECTS | Node.FLAG_LOCAL_RESULTS;
return this;
}
public boolean areAllFlagsSet() {
return value == Node.SIDE_EFFECTS_ALL;
}
/**
* Preserve the return result flag, but clear the others:
* no global state change, no throws, no this change, no arguments change
*/
public void clearSideEffectFlags() {
value |= Node.NO_SIDE_EFFECTS;
}
public SideEffectFlags setMutatesGlobalState() {
// Modify global means everything must be assumed to be modified.
removeFlag(Node.FLAG_GLOBAL_STATE_UNMODIFIED);
removeFlag(Node.FLAG_ARGUMENTS_UNMODIFIED);
removeFlag(Node.FLAG_THIS_UNMODIFIED);
return this;
}
public SideEffectFlags setThrows() {
removeFlag(Node.FLAG_NO_THROWS);
return this;
}
public SideEffectFlags setMutatesThis() {
removeFlag(Node.FLAG_THIS_UNMODIFIED);
return this;
}
public SideEffectFlags setMutatesArguments() {
removeFlag(Node.FLAG_ARGUMENTS_UNMODIFIED);
return this;
}
public SideEffectFlags setReturnsTainted() {
removeFlag(Node.FLAG_LOCAL_RESULTS);
return this;
}
private void removeFlag(int flag) {
value &= ~flag;
}
}
/**
* @return Whether the only side-effect is "modifies this"
*/
public boolean isOnlyModifiesThisCall() {
return areBitFlagsSet(
getSideEffectFlags() & Node.NO_SIDE_EFFECTS,
Node.
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>FLAG_GLOBAL_STATE_UNMODIFIED
| Node.FLAG_ARGUMENTS_UNMODIFIED
| Node.FLAG_NO_THROWS);
}
/**
* @return Whether the only side-effect is "modifies arguments"
*/
public boolean isOnlyModifiesArgumentsCall() {
return areBitFlagsSet(
getSideEffectFlags() & Node.NO_SIDE_EFFECTS,
Node.FLAG_GLOBAL_STATE_UNMODIFIED
| Node.FLAG_THIS_UNMODIFIED
| Node.FLAG_NO_THROWS);
}
/**
* Returns true if this node is a function or constructor call that
* has no side effects.
*/
public boolean isNoSideEffectsCall() {
return areBitFlagsSet(getSideEffectFlags(), NO_SIDE_EFFECTS);
}
/**
* Returns true if this node is a function or constructor call that
* returns a primitive or a local object (an object that has no other
* references).
*/
public boolean isLocalResultCall() {
return areBitFlagsSet(getSideEffectFlags(), FLAG_LOCAL_RESULTS);
}
/** Returns true if this is a new/call that may mutate its arguments. */
public boolean mayMutateArguments() {
return !areBitFlagsSet(getSideEffectFlags(), FLAG_ARGUMENTS_UNMODIFIED);
}
/** Returns true if this is a new/call that may mutate global state or throw. */
public boolean mayMutateGlobalStateOrThrow() {
return !areBitFlagsSet(getSideEffectFlags(),
FLAG_GLOBAL_STATE_UNMODIFIED | FLAG_NO_THROWS);
}
/**
* returns true if all the flags are set in value.
*/
private boolean areBitFlagsSet(int value, int flags) {
return (value & flags) == flags;
}
/**
* This should only be called for STRING nodes children of OBJECTLIT.
*/
public boolean isQuotedString() {
return false;
}
/**
* This should only be called for STRING nodes children of OBJECTLIT.
*/
public void setQuotedString() {
throw new IllegalStateException("not a StringNode");
}
static class NodeMismatch {
final Node nodeA;
final Node nodeB;
NodeMismatch(Node nodeA, Node nodeB) {
this.nodeA = nodeA;
this.nodeB = nodeB;
}
@Override
public boolean equals(Object object) {
if (object instanceof NodeMismatch) {
NodeMismatch that = (NodeMismatch) object;
return that.nodeA.equals(this.nodeA) && that.nodeB.equals(this.nodeB);
}
return false;
}
@Override
public int hashCode() {
return Objects.hashCode(nodeA, nodeB);
}
}
/*** AST type check methods ***/
public boolean isAdd() {
return this.getType() == Token.ADD;
}
public boolean isAnd() {
return this.getType() == Token.AND;
}
public boolean isArrayLit() {
return this.getType() == Token.ARRAYLIT;
}
public boolean isAssign() {
return this.getType() == Token.ASSIGN;
}
public boolean isAssignAdd() {
return this.getType()
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> == Token.ASSIGN_ADD;
}
public boolean isBlock() {
return this.getType() == Token.BLOCK;
}
public boolean isBreak() {
return this.getType() == Token.BREAK;
}
public boolean isCall() {
return this.getType() == Token.CALL;
}
public boolean isCase() {
return this.getType() == Token.CASE;
}
public boolean isCast() {
return this.getType() == Token.CAST;
}
public boolean isCatch() {
return this.getType() == Token.CATCH;
}
public boolean isComma() {
return this.getType() == Token.COMMA;
}
public boolean isContinue() {
return this.getType() == Token.CONTINUE;
}
public boolean isDebugger() {
return this.getType() == Token.DEBUGGER;
}
public boolean isDec() {
return this.getType() == Token.DEC;
}
public boolean isDefaultCase() {
return this.getType() == Token.DEFAULT_CASE;
}
public boolean isDelProp() {
return this.getType() == Token.DELPROP;
}
public boolean isDo() {
return this.getType() == Token.DO;
}
public boolean isEmpty() {
return this.getType() == Token.EMPTY;
}
public boolean isExprResult() {
return this.getType() == Token.EXPR_RESULT;
}
public boolean isFalse() {
return this.getType() == Token.FALSE;
}
public boolean isFor() {
return this.getType() == Token.FOR;
}
public boolean isFunction() {
return this.getType() == Token.FUNCTION;
}
public boolean isGetterDef() {
return this.getType() == Token.GETTER_DEF;
}
public boolean isGetElem() {
return this.getType() == Token.GETELEM;
}
public boolean isGetProp() {
return this.getType() == Token.GETPROP;
}
public boolean isHook() {
return this.getType() == Token.HOOK;
}
public boolean isIf() {
return this.getType() == Token.IF;
}
public boolean isIn() {
return this.getType() == Token.IN;
}
public boolean isInc() {
return this.getType() == Token.INC;
}
public boolean isInstanceOf() {
return this.getType() == Token.INSTANCEOF;
}
public boolean isLabel() {
return this.getType() == Token.LABEL;
}
public boolean isLabelName() {
return this.getType() == Token.LABEL_NAME;
}
public boolean isName() {
return this.getType() == Token.NAME;
}
public boolean isNE() {
return this.getType() == Token.NE;
}
public boolean isNew() {
return this.getType() == Token.NEW;
}
public boolean isNot() {
return this.getType() == Token.NOT;
}
public boolean isNull() {
return this.getType() == Token.NULL;
}
public boolean isNumber() {
return this.getType() == Token.NUMBER;
}
public boolean isObjectLit() {
return this.getType() ==
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> Token.OBJECTLIT;
}
public boolean isOr() {
return this.getType() == Token.OR;
}
public boolean isParamList() {
return this.getType() == Token.PARAM_LIST;
}
public boolean isRegExp() {
return this.getType() == Token.REGEXP;
}
public boolean isReturn() {
return this.getType() == Token.RETURN;
}
public boolean isScript() {
return this.getType() == Token.SCRIPT;
}
public boolean isSetterDef() {
return this.getType() == Token.SETTER_DEF;
}
public boolean isString() {
return this.getType() == Token.STRING;
}
public boolean isStringKey() {
return this.getType() == Token.STRING_KEY;
}
public boolean isSwitch() {
return this.getType() == Token.SWITCH;
}
public boolean isThis() {
return this.getType() == Token.THIS;
}
public boolean isThrow() {
return this.getType() == Token.THROW;
}
public boolean isTrue() {
return this.getType() == Token.TRUE;
}
public boolean isTry() {
return this.getType() == Token.TRY;
}
public boolean isTypeOf() {
return this.getType() == Token.TYPEOF;
}
public boolean isVar() {
return this.getType() == Token.VAR;
}
public boolean isVoid() {
return this.getType() == Token.VOID;
}
public boolean isWhile() {
return this.getType() == Token.WHILE;
}
public boolean isWith() {
return this.getType() == Token.WITH;
}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.collect.Maps;
import com.google.javascript.jscomp.NodeTraversal.AbstractShallowCallback;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.JSDocInfo.Visibility;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.Map;
/**
* Insures '@constructor X' has a 'goog.provide("X")' .
*
*/
class CheckProvides implements HotSwapCompilerPass {
private final AbstractCompiler compiler;
private final CheckLevel checkLevel;
private final CodingConvention codingConvention;
static final DiagnosticType MISSING_PROVIDE_WARNING = DiagnosticType.disabled(
"JSC_MISSING_PROVIDE",
"missing goog.provide(''{0}'')");
CheckProvides(AbstractCompiler compiler, CheckLevel checkLevel) {
this.compiler = compiler;
this.checkLevel = checkLevel;
this.codingConvention = compiler.getCodingConvention();
}
@Override
public void process(Node externs, Node root) {
hotSwapScript(root, null);
}
@Override
public void hotSwapScript(Node scriptRoot, Node originalRoot) {
CheckProvidesCallback callback =
new CheckProvidesCallback(codingConvention);
new NodeTraversal(compiler, callback).traverse(scriptRoot);
}
private class CheckProvidesCallback extends AbstractShallowCallback {
private final Map<String, Node> provides = Maps.newHashMap();
private final Map<String, Node> ctors = Maps.newHashMap();
private final CodingConvention convention;
CheckProvidesCallback(CodingConvention convention){
this.convention = convention;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.CALL:
String providedClassName =
codingConvention.extractClassNameIfProvide(n, parent);
if (providedClassName != null) {
provides.put(providedClassName, n);
}
break;
case Token.FUNCTION:
visitFunctionNode(n, parent);
break;
case Token.SCRIPT:
visitScriptNode();
}
}
private void visitFunctionNode(Node n, Node parent) {
Node name = null;
JSDocInfo info = parent.getJSDocInfo();
if (info != null && info.isConstructor()) {
name = parent.getFirstChild();
} else {
// look to the
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> child, maybe it's a named function
info = n.getJSDocInfo();
if (info != null && info.isConstructor()) {
name = n.getFirstChild();
}
}
if (name != null && name.isQualifiedName()) {
String qualifiedName = name.getQualifiedName();
if (!this.convention.isPrivate(qualifiedName)) {
Visibility visibility = info.getVisibility();
if (!visibility.equals(JSDocInfo.Visibility.PRIVATE)) {
ctors.put(qualifiedName, name);
}
}
}
}
private void visitScriptNode() {
for (Map.Entry<String, Node> ctorEntry : ctors.entrySet()) {
String ctor = ctorEntry.getKey();
int index = -1;
boolean found = false;
do {
index = ctor.indexOf('.', index + 1);
String provideKey = index == -1 ? ctor : ctor.substring(0, index);
if (provides.containsKey(provideKey)) {
found = true;
break;
}
} while (index != -1);
if (!found) {
Node n = ctorEntry.getValue();
compiler.report(
JSError.make(n.getSourceFileName(), n,
checkLevel, MISSING_PROVIDE_WARNING, ctorEntry.getKey()));
}
}
provides.clear();
ctors.clear();
}
}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>/*
* Copyright 2006 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.javascript.jscomp.Scope.Var;
import com.google.javascript.jscomp.type.ClosureReverseAbstractInterpreter;
import com.google.javascript.jscomp.type.SemanticReverseAbstractInterpreter;
import com.google.javascript.rhino.InputId;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.JSTypeNative;
import com.google.javascript.rhino.jstype.ObjectType;
import com.google.javascript.rhino.testing.Asserts;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
/**
* Tests {@link TypeCheck}.
*
*/
public class TypeCheckTest extends CompilerTypeTestCase {
private CheckLevel reportMissingOverrides = CheckLevel.WARNING;
private static final String SUGGESTION_CLASS =
"/** @constructor\n */\n" +
"function Suggest() {}\n" +
"Suggest.prototype.a = 1;\n" +
"Suggest.prototype.veryPossible = 1;\n" +
"Suggest.prototype.veryPossible2 = 1;\n";
@Override
public void setUp() throws Exception {
super.setUp();
reportMissingOverrides = CheckLevel.WARNING;
}
public void testInitialTypingScope() {
Scope s = new TypedScopeCreator(compiler,
CodingConventions.getDefault()).createInitialScope(
new Node(Token.BLOCK));
assertTypeEquals(ARRAY_FUNCTION_TYPE, s.getVar("Array").getType());
assertTypeEquals(BOOLEAN_OBJECT_FUNCTION_TYPE,
s.getVar("Boolean").getType());
assertTypeEquals(DATE_FUNCTION_TYPE, s.getVar("Date").getType());
assertTypeEquals(ERROR_FUNCTION_TYPE, s.getVar("Error").getType());
assertTypeEquals(EVAL_ERROR_FUNCTION_TYPE,
s.getVar("EvalError").getType());
assertTypeEquals(NUMBER_OBJECT_FUNCTION_TYPE,
s.getVar("Number").getType());
assertTypeEquals(OBJECT_FUNCTION_TYPE, s.getVar("Object").getType());
assertTypeEquals
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>(RANGE_ERROR_FUNCTION_TYPE,
s.getVar("RangeError").getType());
assertTypeEquals(REFERENCE_ERROR_FUNCTION_TYPE,
s.getVar("ReferenceError").getType());
assertTypeEquals(REGEXP_FUNCTION_TYPE, s.getVar("RegExp").getType());
assertTypeEquals(STRING_OBJECT_FUNCTION_TYPE,
s.getVar("String").getType());
assertTypeEquals(SYNTAX_ERROR_FUNCTION_TYPE,
s.getVar("SyntaxError").getType());
assertTypeEquals(TYPE_ERROR_FUNCTION_TYPE,
s.getVar("TypeError").getType());
assertTypeEquals(URI_ERROR_FUNCTION_TYPE,
s.getVar("URIError").getType());
}
public void testPrivateType() throws Exception {
testTypes(
"/** @private {number} */ var x = false;",
"initializing variable\n" +
"found : boolean\n" +
"required: number");
}
public void testTypeCheck1() throws Exception {
testTypes("/**@return {void}*/function foo(){ if (foo()) return; }");
}
public void testTypeCheck2() throws Exception {
testTypes("/**@return {void}*/function foo(){ var x=foo(); x--; }",
"increment/decrement\n" +
"found : undefined\n" +
"required: number");
}
public void testTypeCheck4() throws Exception {
testTypes("/**@return {void}*/function foo(){ !foo(); }");
}
public void testTypeCheck5() throws Exception {
testTypes("/**@return {void}*/function foo(){ var a = +foo(); }",
"sign operator\n" +
"found : undefined\n" +
"required: number");
}
public void testTypeCheck6() throws Exception {
testTypes(
"/**@return {void}*/function foo(){" +
"/** @type {undefined|number} */var a;if (a == foo())return;}");
}
public void testTypeCheck8() throws Exception {
testTypes("/**@return {void}*/function foo(){do {} while (foo());}");
}
public void testTypeCheck9() throws Exception {
testTypes("/**@return {void}*/function foo(){while (foo());}");
}
public void testTypeCheck10() throws Exception {
testTypes("/**@return {void}*/function foo(){for (;foo(););}");
}
public void testTypeCheck11() throws Exception {
testTypes("/**@type !Number */var a;" +
"/**@type !String */var b;" +
"a = b;",
"assignment\n" +
"found : String\n" +
"required: Number");
}
public void testTypeCheck12() throws Exception {
testTypes("/**@return {!Object}*/function foo(){var a = 3^foo();}",
"bad right operand to bitwise operator\n" +
"found : Object\n" +
"required: (boolean|null|number|string|undefined)");
}
public void testTypeCheck13() throws Exception {
testTypes("/**@type {!Number|!String}*/var i; i=/xx/;",
"assignment\n
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> = function b(){};");
// a declared, b is not
assertTrue(p.scope.isDeclared("a", false));
assertFalse(p.scope.isDeclared("b", false));
// checking that a has the correct assigned type
assertEquals("function (): undefined",
p.scope.getVar("a").getType().toString());
}
public void testScoping11() throws Exception {
// named function expressions create a binding in their body only
// the return is wrong but the assignment is OK since the type of b is ?
testTypes(
"/** @return {number} */var a = function b(){ return b };",
"inconsistent return type\n" +
"found : function (): number\n" +
"required: number");
}
public void testScoping12() throws Exception {
testTypes(
"/** @constructor */ function F() {}" +
"/** @type {number} */ F.prototype.bar = 3;" +
"/** @param {!F} f */ function g(f) {" +
" /** @return {string} */" +
" function h() {" +
" return f.bar;" +
" }" +
"}",
"inconsistent return type\n" +
"found : number\n" +
"required: string");
}
public void testFunctionArguments1() throws Exception {
testFunctionType(
"/** @param {number} a\n@return {string} */" +
"function f(a) {}",
"function (number): string");
}
public void testFunctionArguments2() throws Exception {
testFunctionType(
"/** @param {number} opt_a\n@return {string} */" +
"function f(opt_a) {}",
"function (number=): string");
}
public void testFunctionArguments3() throws Exception {
testFunctionType(
"/** @param {number} b\n@return {string} */" +
"function f(a,b) {}",
"function (?, number): string");
}
public void testFunctionArguments4() throws Exception {
testFunctionType(
"/** @param {number} opt_a\n@return {string} */" +
"function f(a,opt_a) {}",
"function (?, number=): string");
}
public void testFunctionArguments5() throws Exception {
testTypes(
"function a(opt_a,a) {}",
"optional arguments must be at the end");
}
public void testFunctionArguments6() throws Exception {
testTypes(
"function a(var_args,a) {}",
"variable length argument must be last");
}
public void testFunctionArguments7() throws Exception {
testTypes(
"/** @param {number} opt_a\n@return {string} */" +
"function a(a,opt_a,var_args) {}");
}
public void testFunctionArguments8() throws Exception {
testTypes(
"function a(a,opt_a,var_args,b) {}",
"variable length argument must be last");
}
public void testFunctionArguments9() throws Exception {
// testing that only one error is reported
testTypes
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> Object");
}
public void testArrayAccess7() throws Exception {
testTypes("var bar = void 0; bar[0];",
"only arrays or objects can be accessed\n" +
"found : undefined\n" +
"required: Object");
}
public void testArrayAccess8() throws Exception {
// Verifies that we don't emit two warnings, because
// the var has been dereferenced after the first one.
testTypes("var bar = void 0; bar[0]; bar[1];",
"only arrays or objects can be accessed\n" +
"found : undefined\n" +
"required: Object");
}
public void testArrayAccess9() throws Exception {
testTypes("/** @return {?Array} */ function f() { return []; }" +
"f()[{}]",
"array access\n" +
"found : {}\n" +
"required: number");
}
public void testPropAccess() throws Exception {
testTypes("/** @param {*} x */var f = function(x) {\n" +
"var o = String(x);\n" +
"if (typeof o['a'] != 'undefined') { return o['a']; }\n" +
"return null;\n" +
"};");
}
public void testPropAccess2() throws Exception {
testTypes("var bar = void 0; bar.baz;",
"No properties on this expression\n" +
"found : undefined\n" +
"required: Object");
}
public void testPropAccess3() throws Exception {
// Verifies that we don't emit two warnings, because
// the var has been dereferenced after the first one.
testTypes("var bar = void 0; bar.baz; bar.bax;",
"No properties on this expression\n" +
"found : undefined\n" +
"required: Object");
}
public void testPropAccess4() throws Exception {
testTypes("/** @param {*} x */ function f(x) { return x['hi']; }");
}
public void testSwitchCase1() throws Exception {
testTypes("/**@type number*/var a;" +
"/**@type string*/var b;" +
"switch(a){case b:;}",
"case expression doesn't match switch\n" +
"found : string\n" +
"required: number");
}
public void testSwitchCase2() throws Exception {
testTypes("var a = null; switch (typeof a) { case 'foo': }");
}
public void testVar1() throws Exception {
TypeCheckResult p =
parseAndTypeCheckWithScope("/** @type {(string,null)} */var a = null");
assertTypeEquals(createUnionType(STRING_TYPE, NULL_TYPE),
p.scope.getVar("a").getType());
}
public void testVar2() throws Exception {
testTypes("/** @type {Function} */ var a = function(){}");
}
public void testVar3() throws Exception {
TypeCheckResult p = parseAndTypeCheckWithScope("var a = 3;");
assertTypeEquals(NUMBER_TYPE, p.scope.getVar("a").getType());
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> }
public void testVar4() throws Exception {
TypeCheckResult p = parseAndTypeCheckWithScope(
"var a = 3; a = 'string';");
assertTypeEquals(createUnionType(STRING_TYPE, NUMBER_TYPE),
p.scope.getVar("a").getType());
}
public void testVar5() throws Exception {
testTypes("var goog = {};" +
"/** @type string */goog.foo = 'hello';" +
"/** @type number */var a = goog.foo;",
"initializing variable\n" +
"found : string\n" +
"required: number");
}
public void testVar6() throws Exception {
testTypes(
"function f() {" +
" return function() {" +
" /** @type {!Date} */" +
" var a = 7;" +
" };" +
"}",
"initializing variable\n" +
"found : number\n" +
"required: Date");
}
public void testVar7() throws Exception {
testTypes("/** @type number */var a, b;",
"declaration of multiple variables with shared type information");
}
public void testVar8() throws Exception {
testTypes("var a, b;");
}
public void testVar9() throws Exception {
testTypes("/** @enum */var a;",
"enum initializer must be an object literal or an enum");
}
public void testVar10() throws Exception {
testTypes("/** @type !Number */var foo = 'abc';",
"initializing variable\n" +
"found : string\n" +
"required: Number");
}
public void testVar11() throws Exception {
testTypes("var /** @type !Date */foo = 'abc';",
"initializing variable\n" +
"found : string\n" +
"required: Date");
}
public void testVar12() throws Exception {
testTypes("var /** @type !Date */foo = 'abc', " +
"/** @type !RegExp */bar = 5;",
new String[] {
"initializing variable\n" +
"found : string\n" +
"required: Date",
"initializing variable\n" +
"found : number\n" +
"required: RegExp"});
}
public void testVar13() throws Exception {
// this caused an NPE
testTypes("var /** @type number */a,a;");
}
public void testVar14() throws Exception {
testTypes("/** @return {number} */ function f() { var x; return x; }",
"inconsistent return type\n" +
"found : undefined\n" +
"required: number");
}
public void testVar15() throws Exception {
testTypes("/** @return {number} */" +
"function f() { var x = x || {}; return x; }",
"inconsistent return type\n" +
"found : {}\n" +
"required: number");
}
public void testAssign1() throws Exception {
testTypes("var goog = {};" +
"/** @
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>() throws Exception {
testTypes("/** @constructor */function A(){}" +
"/** @constructor\n * @extends A */function B(){}" +
"/** @param {B} b" +
"\n @return {(A,undefined)} */function foo(b){return b}");
}
/**
* Tests that assigning two untyped functions to a variable whose type is
* inferred and calling this variable is legal.
*/
public void testBug911118() throws Exception {
// verifying the type assigned to function expressions assigned variables
Scope s = parseAndTypeCheckWithScope("var a = function(){};").scope;
JSType type = s.getVar("a").getType();
assertEquals("function (): undefined", type.toString());
// verifying the bug example
testTypes("function nullFunction() {};" +
"var foo = nullFunction;" +
"foo = function() {};" +
"foo();");
}
public void testBug909000() throws Exception {
testTypes("/** @constructor */function A(){}\n" +
"/** @param {!A} a\n" +
"@return {boolean}*/\n" +
"function y(a) { return a }",
"inconsistent return type\n" +
"found : A\n" +
"required: boolean");
}
public void testBug930117() throws Exception {
testTypes(
"/** @param {boolean} x */function f(x){}" +
"f(null);",
"actual parameter 1 of f does not match formal parameter\n" +
"found : null\n" +
"required: boolean");
}
public void testBug1484445() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}" +
"/** @type {number?} */ Foo.prototype.bar = null;" +
"/** @type {number?} */ Foo.prototype.baz = null;" +
"/** @param {Foo} foo */" +
"function f(foo) {" +
" while (true) {" +
" if (foo.bar == null && foo.baz == null) {" +
" foo.bar;" +
" }" +
" }" +
"}");
}
public void testBug1859535() throws Exception {
testTypes(
"/**\n" +
" * @param {Function} childCtor Child class.\n" +
" * @param {Function} parentCtor Parent class.\n" +
" */" +
"var inherits = function(childCtor, parentCtor) {" +
" /** @constructor */" +
" function tempCtor() {};" +
" tempCtor.prototype = parentCtor.prototype;" +
" childCtor.superClass_ = parentCtor.prototype;" +
" childCtor.prototype = new tempCtor();" +
" /** @override */ childCtor.prototype.constructor = childCtor;" +
"};" +
"/**" +
" * @param {Function} constructor\n" +
" * @param {Object} var_args\n" +
" * @return {
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>" +
"var a = new A();");
JSType aType = p.scope.getVar("a").getType();
assertTrue(aType instanceof ObjectType);
ObjectType aObjectType = (ObjectType) aType;
assertEquals("A", aObjectType.getConstructor().getReferenceName());
}
public void testNew7() throws Exception {
testTypes("/** @param {Function} opt_constructor */" +
"function foo(opt_constructor) {" +
"if (opt_constructor) { new opt_constructor; }" +
"}");
}
public void testNew8() throws Exception {
testTypes("/** @param {Function} opt_constructor */" +
"function foo(opt_constructor) {" +
"new opt_constructor;" +
"}");
}
public void testNew9() throws Exception {
testTypes("/** @param {Function} opt_constructor */" +
"function foo(opt_constructor) {" +
"new (opt_constructor || Array);" +
"}");
}
public void testNew10() throws Exception {
testTypes("var goog = {};" +
"/** @param {Function} opt_constructor */" +
"goog.Foo = function (opt_constructor) {" +
"new (opt_constructor || Array);" +
"}");
}
public void testNew11() throws Exception {
testTypes("/** @param {Function} c1 */" +
"function f(c1) {" +
" var c2 = function(){};" +
" c1.prototype = new c2;" +
"}", TypeCheck.NOT_A_CONSTRUCTOR);
}
public void testNew12() throws Exception {
TypeCheckResult p = parseAndTypeCheckWithScope("var a = new Array();");
Var a = p.scope.getVar("a");
assertTypeEquals(ARRAY_TYPE, a.getType());
}
public void testNew13() throws Exception {
TypeCheckResult p = parseAndTypeCheckWithScope(
"/** @constructor */function FooBar(){};" +
"var a = new FooBar();");
Var a = p.scope.getVar("a");
assertTrue(a.getType() instanceof ObjectType);
assertEquals("FooBar", a.getType().toString());
}
public void testNew14() throws Exception {
TypeCheckResult p = parseAndTypeCheckWithScope(
"/** @constructor */var FooBar = function(){};" +
"var a = new FooBar();");
Var a = p.scope.getVar("a");
assertTrue(a.getType() instanceof ObjectType);
assertEquals("FooBar", a.getType().toString());
}
public void testNew15() throws Exception {
TypeCheckResult p = parseAndTypeCheckWithScope(
"var goog = {};" +
"/** @constructor */goog.A = function(){};" +
"var a = new goog.A();");
Var a = p.scope.getVar("a");
assertTrue(a.getType() instanceof ObjectType);
assertEquals("goog.A", a.getType().toString());
}
public void testNew16() throws Exception {
testTypes(
"/** \n" +
" * @param {string} x \n" +
" * @constructor \n"
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> == 'boolean' ||" +
"typeof 123 == 'undefined' ||" +
"typeof 123 == 'function' ||" +
"typeof 123 == 'object' ||" +
"typeof 123 == 'unknown'); }");
}
public void testConstructorType1() throws Exception {
testTypes("/**@constructor*/function Foo(){}" +
"/**@type{!Foo}*/var f = new Date();",
"initializing variable\n" +
"found : Date\n" +
"required: Foo");
}
public void testConstructorType2() throws Exception {
testTypes("/**@constructor*/function Foo(){\n" +
"/**@type{Number}*/this.bar = new Number(5);\n" +
"}\n" +
"/**@type{Foo}*/var f = new Foo();\n" +
"/**@type{Number}*/var n = f.bar;");
}
public void testConstructorType3() throws Exception {
// Reverse the declaration order so that we know that Foo is getting set
// even on an out-of-order declaration sequence.
testTypes("/**@type{Foo}*/var f = new Foo();\n" +
"/**@type{Number}*/var n = f.bar;" +
"/**@constructor*/function Foo(){\n" +
"/**@type{Number}*/this.bar = new Number(5);\n" +
"}\n");
}
public void testConstructorType4() throws Exception {
testTypes("/**@constructor*/function Foo(){\n" +
"/**@type{!Number}*/this.bar = new Number(5);\n" +
"}\n" +
"/**@type{!Foo}*/var f = new Foo();\n" +
"/**@type{!String}*/var n = f.bar;",
"initializing variable\n" +
"found : Number\n" +
"required: String");
}
public void testConstructorType5() throws Exception {
testTypes("/**@constructor*/function Foo(){}\n" +
"if (Foo){}\n");
}
public void testConstructorType6() throws Exception {
testTypes("/** @constructor */\n" +
"function bar() {}\n" +
"function _foo() {\n" +
" /** @param {bar} x */\n" +
" function f(x) {}\n" +
"}");
}
public void testConstructorType7() throws Exception {
TypeCheckResult p =
parseAndTypeCheckWithScope("/** @constructor */function A(){};");
JSType type = p.scope.getVar("A").getType();
assertTrue(type instanceof FunctionType);
FunctionType fType = (FunctionType) type;
assertEquals("A", fType.getReferenceName());
}
public void testConstructorType8() throws Exception {
testTypes(
"var ns = {};" +
"ns.create = function() { return function() {}; };" +
"/** @constructor */ ns.Foo = ns.create();" +
"ns.Foo.prototype = {x: 0, y: 0};" +
"/**\n" +
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> {};\n" +
"/** @type {number} */ n.x = 1;\n" +
"/** @return {boolean} */function f() { return n.x; }",
"inconsistent return type\n" +
"found : number\n" +
"required: boolean");
}
public void testDefinePropertyOnNullableObject2() throws Exception {
testTypes("/** @constructor */ var T = function() {};\n" +
"/** @param {T} t\n@return {boolean} */function f(t) {\n" +
"t.x = 1; return t.x; }",
"inconsistent return type\n" +
"found : number\n" +
"required: boolean");
}
public void testUnknownConstructorInstanceType1() throws Exception {
testTypes("/** @return {Array} */ function g(f) { return new f(); }");
}
public void testUnknownConstructorInstanceType2() throws Exception {
testTypes("function g(f) { return /** @type Array */(new f()); }");
}
public void testUnknownConstructorInstanceType3() throws Exception {
testTypes("function g(f) { var x = new f(); x.a = 1; return x; }");
}
public void testUnknownPrototypeChain() throws Exception {
testTypes("/**\n" +
"* @param {Object} co\n" +
" * @return {Object}\n" +
" */\n" +
"function inst(co) {\n" +
" /** @constructor */\n" +
" var c = function() {};\n" +
" c.prototype = co.prototype;\n" +
" return new c;\n" +
"}");
}
public void testNamespacedConstructor() throws Exception {
Node root = parseAndTypeCheck(
"var goog = {};" +
"/** @constructor */ goog.MyClass = function() {};" +
"/** @return {!goog.MyClass} */ " +
"function foo() { return new goog.MyClass(); }");
JSType typeOfFoo = root.getLastChild().getJSType();
assert(typeOfFoo instanceof FunctionType);
JSType retType = ((FunctionType) typeOfFoo).getReturnType();
assert(retType instanceof ObjectType);
assertEquals("goog.MyClass", ((ObjectType) retType).getReferenceName());
}
public void testComplexNamespace() throws Exception {
String js =
"var goog = {};" +
"goog.foo = {};" +
"goog.foo.bar = 5;";
TypeCheckResult p = parseAndTypeCheckWithScope(js);
// goog type in the scope
JSType googScopeType = p.scope.getVar("goog").getType();
assertTrue(googScopeType instanceof ObjectType);
assertTrue("foo property not present on goog type",
((ObjectType) googScopeType).hasProperty("foo"));
assertFalse("bar property present on goog type",
((ObjectType) googScopeType).hasProperty("bar"));
// goog type on the VAR node
Node varNode = p.root.getFirstChild();
assertEquals(Token.VAR, varNode.getType());
JSType googNodeType = varNode.getFirstChild().
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>getJSType();
assertTrue(googNodeType instanceof ObjectType);
// goog scope type and goog type on VAR node must be the same
assertTrue(googScopeType == googNodeType);
// goog type on the left of the GETPROP node (under fist ASSIGN)
Node getpropFoo1 = varNode.getNext().getFirstChild().getFirstChild();
assertEquals(Token.GETPROP, getpropFoo1.getType());
assertEquals("goog", getpropFoo1.getFirstChild().getString());
JSType googGetpropFoo1Type = getpropFoo1.getFirstChild().getJSType();
assertTrue(googGetpropFoo1Type instanceof ObjectType);
// still the same type as the one on the variable
assertTrue(googGetpropFoo1Type == googScopeType);
// the foo property should be defined on goog
JSType googFooType = ((ObjectType) googScopeType).getPropertyType("foo");
assertTrue(googFooType instanceof ObjectType);
// goog type on the left of the GETPROP lower level node
// (under second ASSIGN)
Node getpropFoo2 = varNode.getNext().getNext()
.getFirstChild().getFirstChild().getFirstChild();
assertEquals(Token.GETPROP, getpropFoo2.getType());
assertEquals("goog", getpropFoo2.getFirstChild().getString());
JSType googGetpropFoo2Type = getpropFoo2.getFirstChild().getJSType();
assertTrue(googGetpropFoo2Type instanceof ObjectType);
// still the same type as the one on the variable
assertTrue(googGetpropFoo2Type == googScopeType);
// goog.foo type on the left of the top-level GETPROP node
// (under second ASSIGN)
JSType googFooGetprop2Type = getpropFoo2.getJSType();
assertTrue("goog.foo incorrectly annotated in goog.foo.bar selection",
googFooGetprop2Type instanceof ObjectType);
ObjectType googFooGetprop2ObjectType = (ObjectType) googFooGetprop2Type;
assertFalse("foo property present on goog.foo type",
googFooGetprop2ObjectType.hasProperty("foo"));
assertTrue("bar property not present on goog.foo type",
googFooGetprop2ObjectType.hasProperty("bar"));
assertTypeEquals("bar property on goog.foo type incorrectly inferred",
NUMBER_TYPE, googFooGetprop2ObjectType.getPropertyType("bar"));
}
public void testAddingMethodsUsingPrototypeIdiomSimpleNamespace()
throws Exception {
Node js1Node = parseAndTypeCheck(
"/** @constructor */function A() {}" +
"A.prototype.m1 = 5");
ObjectType instanceType = getInstanceType(js1Node);
assertEquals(NATIVE_PROPERTIES_COUNT + 1,
instanceType.getPropertiesCount());
checkObjectType(instanceType, "m1", NUMBER_TYPE);
}
public void testAddingMethodsUsingPrototypeIdiomComplexNamespace1()
throws Exception {
TypeCheckResult p = parseAndTypeCheckWithScope(
"var goog = {};" +
"goog.A = /** @constructor */function() {};" +
"/** @type number */goog.A.prototype.m1 = 5");
testAddingMethodsUsingPrototypeIdiomComplexNamespace(p);
}
public void testAddingMethodsUsingPrototypeIdiomComplexNamespace2()
throws Exception {
TypeCheckResult
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> p = parseAndTypeCheckWithScope(
"var goog = {};" +
"/** @constructor */goog.A = function() {};" +
"/** @type number */goog.A.prototype.m1 = 5");
testAddingMethodsUsingPrototypeIdiomComplexNamespace(p);
}
private void testAddingMethodsUsingPrototypeIdiomComplexNamespace(
TypeCheckResult p) {
ObjectType goog = (ObjectType) p.scope.getVar("goog").getType();
assertEquals(NATIVE_PROPERTIES_COUNT + 1, goog.getPropertiesCount());
JSType googA = goog.getPropertyType("A");
assertNotNull(googA);
assertTrue(googA instanceof FunctionType);
FunctionType googAFunction = (FunctionType) googA;
ObjectType classA = googAFunction.getInstanceType();
assertEquals(NATIVE_PROPERTIES_COUNT + 1, classA.getPropertiesCount());
checkObjectType(classA, "m1", NUMBER_TYPE);
}
public void testAddingMethodsPrototypeIdiomAndObjectLiteralSimpleNamespace()
throws Exception {
Node js1Node = parseAndTypeCheck(
"/** @constructor */function A() {}" +
"A.prototype = {m1: 5, m2: true}");
ObjectType instanceType = getInstanceType(js1Node);
assertEquals(NATIVE_PROPERTIES_COUNT + 2,
instanceType.getPropertiesCount());
checkObjectType(instanceType, "m1", NUMBER_TYPE);
checkObjectType(instanceType, "m2", BOOLEAN_TYPE);
}
public void testDontAddMethodsIfNoConstructor()
throws Exception {
Node js1Node = parseAndTypeCheck(
"function A() {}" +
"A.prototype = {m1: 5, m2: true}");
JSType functionAType = js1Node.getFirstChild().getJSType();
assertEquals("function (): undefined", functionAType.toString());
assertTypeEquals(UNKNOWN_TYPE,
U2U_FUNCTION_TYPE.getPropertyType("m1"));
assertTypeEquals(UNKNOWN_TYPE,
U2U_FUNCTION_TYPE.getPropertyType("m2"));
}
public void testFunctionAssignement() throws Exception {
testTypes("/**" +
"* @param {string} ph0" +
"* @param {string} ph1" +
"* @return {string}" +
"*/" +
"function MSG_CALENDAR_ACCESS_ERROR(ph0, ph1) {return ''}" +
"/** @type {Function} */" +
"var MSG_CALENDAR_ADD_ERROR = MSG_CALENDAR_ACCESS_ERROR;");
}
public void testAddMethodsPrototypeTwoWays() throws Exception {
Node js1Node = parseAndTypeCheck(
"/** @constructor */function A() {}" +
"A.prototype = {m1: 5, m2: true};" +
"A.prototype.m3 = 'third property!';");
ObjectType instanceType = getInstanceType(js1Node);
assertEquals("A", instanceType.toString());
assertEquals(NATIVE_PROPERTIES_COUNT + 3,
instanceType.getPropertiesCount());
checkObjectType(instanceType, "m1", NUMBER_TYPE);
checkObjectType(instanceType, "m2", BOOLEAN_TYPE);
checkObjectType(
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> { return 3 };",
"inconsistent return type\n" +
"found : number\n" +
"required: string");
}
public void testInterfacePropertyNotImplemented() throws Exception {
testTypes(
"/** @interface */function Int() {};" +
"/** @desc description */Int.prototype.foo = function() {};" +
"/** @constructor\n @implements {Int} */function Foo() {};",
"property foo on interface Int is not implemented by type Foo");
}
public void testInterfacePropertyNotImplemented2() throws Exception {
testTypes(
"/** @interface */function Int() {};" +
"/** @desc description */Int.prototype.foo = function() {};" +
"/** @interface \n @extends {Int} */function Int2() {};" +
"/** @constructor\n @implements {Int2} */function Foo() {};",
"property foo on interface Int is not implemented by type Foo");
}
/**
* Verify that templatized interfaces enforce their template type values.
*/
public void testInterfacePropertyNotImplemented3() throws Exception {
testTypes(
"/** @interface\n @template T */function Int() {};" +
"/** @desc description\n @return {T} */Int.prototype.foo = function() {};" +
"/** @constructor\n @implements {Int.<string>} */function Foo() {};" +
"/** @return {number}\n @override */Foo.prototype.foo = function() {};",
"mismatch of the foo property type and the type of the property it " +
"overrides from interface Int\n" +
"original: function (this:Int): string\n" +
"override: function (this:Foo): number");
}
public void testStubConstructorImplementingInterface() throws Exception {
// This does not throw a warning for unimplemented property because Foo is
// just a stub.
testTypes(
// externs
"/** @interface */ function Int() {}\n" +
"/** @desc description */Int.prototype.foo = function() {};" +
"/** @constructor \n @implements {Int} */ var Foo;\n",
"", null, false);
}
public void testObjectLiteral() throws Exception {
Node n = parseAndTypeCheck("var a = {m1: 7, m2: 'hello'}");
Node nameNode = n.getFirstChild().getFirstChild();
Node objectNode = nameNode.getFirstChild();
// node extraction
assertEquals(Token.NAME, nameNode.getType());
assertEquals(Token.OBJECTLIT, objectNode.getType());
// value's type
ObjectType objectType =
(ObjectType) objectNode.getJSType();
assertTypeEquals(NUMBER_TYPE, objectType.getPropertyType("m1"));
assertTypeEquals(STRING_TYPE, objectType.getPropertyType("m2"));
// variable's type
assertTypeEquals(objectType, nameNode.getJSType());
}
public void testObjectLiteralDeclaration1() throws Exception {
testTypes(
"var x = {" +
"/** @type {boolean} */ abc: true," +
"/** @type {number} */ 'def': 0," +
"/** @type {string} */ 3: 'fgh
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> = {A: 1,B: 2,C: 3};";
assertEquals(100.0, getTypedPercent(js), 0.1);
}
public void testGetTypedPercent6() throws Exception {
String js = "a = {TRUE: 1, FALSE: 0};";
assertEquals(100.0, getTypedPercent(js), 0.1);
}
private double getTypedPercent(String js) throws Exception {
Node n = compiler.parseTestCode(js);
Node externs = new Node(Token.BLOCK);
Node externAndJsRoot = new Node(Token.BLOCK, externs, n);
externAndJsRoot.setIsSyntheticBlock(true);
TypeCheck t = makeTypeCheck();
t.processForTesting(null, n);
return t.getTypedPercent();
}
private static ObjectType getInstanceType(Node js1Node) {
JSType type = js1Node.getFirstChild().getJSType();
assertNotNull(type);
assertTrue(type instanceof FunctionType);
FunctionType functionType = (FunctionType) type;
assertTrue(functionType.isConstructor());
return functionType.getInstanceType();
}
public void testPrototypePropertyReference() throws Exception {
TypeCheckResult p = parseAndTypeCheckWithScope(""
+ "/** @constructor */\n"
+ "function Foo() {}\n"
+ "/** @param {number} a */\n"
+ "Foo.prototype.bar = function(a){};\n"
+ "/** @param {Foo} f */\n"
+ "function baz(f) {\n"
+ " Foo.prototype.bar.call(f, 3);\n"
+ "}");
assertEquals(0, compiler.getErrorCount());
assertEquals(0, compiler.getWarningCount());
assertTrue(p.scope.getVar("Foo").getType() instanceof FunctionType);
FunctionType fooType = (FunctionType) p.scope.getVar("Foo").getType();
assertEquals("function (this:Foo, number): undefined",
fooType.getPrototype().getPropertyType("bar").toString());
}
public void testResolvingNamedTypes() throws Exception {
String js = ""
+ "/** @constructor */\n"
+ "var Foo = function() {}\n"
+ "/** @param {number} a */\n"
+ "Foo.prototype.foo = function(a) {\n"
+ " return this.baz().toString();\n"
+ "};\n"
+ "/** @return {Baz} */\n"
+ "Foo.prototype.baz = function() { return new Baz(); };\n"
+ "/** @constructor\n"
+ " * @extends Foo */\n"
+ "var Bar = function() {};"
+ "/** @constructor */\n"
+ "var Baz = function() {};";
assertEquals(100.0, getTypedPercent(js), 0.1);
}
public void testMissingProperty1() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}" +
"Foo.prototype.bar = function() { return this.a; };" +
"Foo.prototype.baz = function
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>
Node n = parseAndTypeCheck("var undefined;");
assertTypeEquals(registry.getNativeType(JSTypeNative.VOID_TYPE),
n.getFirstChild().getFirstChild().getJSType());
}
public void testFlowScopeBug1() throws Exception {
Node n = parseAndTypeCheck("/** @param {number} a \n"
+ "* @param {number} b */\n"
+ "function f(a, b) {\n"
+ "/** @type number */"
+ "var i = 0;"
+ "for (; (i + a) < b; ++i) {}}");
// check the type of the add node for i + f
assertTypeEquals(registry.getNativeType(JSTypeNative.NUMBER_TYPE),
n.getFirstChild().getLastChild().getLastChild().getFirstChild()
.getNext().getFirstChild().getJSType());
}
public void testFlowScopeBug2() throws Exception {
Node n = parseAndTypeCheck("/** @constructor */ function Foo() {};\n"
+ "Foo.prototype.hi = false;"
+ "function foo(a, b) {\n"
+ " /** @type Array */"
+ " var arr;"
+ " /** @type number */"
+ " var iter;"
+ " for (iter = 0; iter < arr.length; ++ iter) {"
+ " /** @type Foo */"
+ " var afoo = arr[iter];"
+ " afoo;"
+ " }"
+ "}");
// check the type of afoo when referenced
assertTypeEquals(registry.createNullableType(registry.getType("Foo")),
n.getLastChild().getLastChild().getLastChild().getLastChild()
.getLastChild().getLastChild().getJSType());
}
public void testAddSingletonGetter() {
Node n = parseAndTypeCheck(
"/** @constructor */ function Foo() {};\n" +
"goog.addSingletonGetter(Foo);");
ObjectType o = (ObjectType) n.getFirstChild().getJSType();
assertEquals("function (): Foo",
o.getPropertyType("getInstance").toString());
assertEquals("Foo", o.getPropertyType("instance_").toString());
}
public void testTypeCheckStandaloneAST() throws Exception {
Node n = compiler.parseTestCode("function Foo() { }");
typeCheck(n);
MemoizedScopeCreator scopeCreator = new MemoizedScopeCreator(
new TypedScopeCreator(compiler));
Scope topScope = scopeCreator.createScope(n, null);
Node second = compiler.parseTestCode("new Foo");
Node externs = new Node(Token.BLOCK);
Node externAndJsRoot = new Node(Token.BLOCK, externs, second);
externAndJsRoot.setIsSyntheticBlock(true);
new TypeCheck(
compiler,
new SemanticReverseAbstractInterpreter(
compiler.getCodingConvention(), registry),
registry, topScope, scopeCreator, CheckLevel.WARNING)
.process(null, second);
assertEquals(1, compiler.getWarningCount());
assertEquals("cannot instantiate non-constructor",
compiler.getWarnings()[0].description);
}
public void testUpdateParameterTypeOnClosure() throws Exception {
testTypes(
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>MissingOverrides);
}
void testTypes(String js, String[] warnings) throws Exception {
Node n = compiler.parseTestCode(js);
assertEquals(0, compiler.getErrorCount());
Node externsNode = new Node(Token.BLOCK);
// create a parent node for the extern and source blocks
new Node(Token.BLOCK, externsNode, n);
makeTypeCheck().processForTesting(null, n);
assertEquals(0, compiler.getErrorCount());
if (warnings != null) {
assertEquals(warnings.length, compiler.getWarningCount());
JSError[] messages = compiler.getWarnings();
for (int i = 0; i < warnings.length && i < compiler.getWarningCount();
i++) {
assertEquals(warnings[i], messages[i].description);
}
} else {
assertEquals(0, compiler.getWarningCount());
}
}
String suppressMissingProperty(String ... props) {
String result = "function dummy(x) { ";
for (String prop : props) {
result += "x." + prop + " = 3;";
}
return result + "}";
}
private static class TypeCheckResult {
private final Node root;
private final Scope scope;
private TypeCheckResult(Node root, Scope scope) {
this.root = root;
this.scope = scope;
}
}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> candidate == entry);
prioritizeFromEntryNode(candidate);
}
}
}
// At this point, all reachable nodes have been given a priority, but
// unreachable nodes have not been given a priority. Put them last.
// Presumably, it doesn't really matter what priority they get, since
// this shouldn't happen in real code.
for (DiGraphNode<Node, Branch> candidate : cfg.getDirectedGraphNodes()) {
if (!nodePriorities.containsKey(candidate)) {
nodePriorities.put(candidate, ++priorityCounter);
}
}
// Again, the implicit return node is always last.
nodePriorities.put(cfg.getImplicitReturn(), ++priorityCounter);
}
/**
* Given an entry node, find all the nodes reachable from that node
* and prioritize them.
*/
private void prioritizeFromEntryNode(DiGraphNode<Node, Branch> entry) {
PriorityQueue<DiGraphNode<Node, Branch>> worklist =
new PriorityQueue<DiGraphNode<Node, Branch>>(10, priorityComparator);
worklist.add(entry);
while (!worklist.isEmpty()) {
DiGraphNode<Node, Branch> current = worklist.remove();
if (nodePriorities.containsKey(current)) {
continue;
}
nodePriorities.put(current, ++priorityCounter);
List<DiGraphNode<Node, Branch>> successors =
cfg.getDirectedSuccNodes(current);
for (DiGraphNode<Node, Branch> candidate : successors) {
worklist.add(candidate);
}
}
}
@Override
public boolean shouldTraverse(
NodeTraversal nodeTraversal, Node n, Node parent) {
astPosition.put(n, astPositionCounter++);
switch (n.getType()) {
case Token.FUNCTION:
if (shouldTraverseFunctions || n == cfg.getEntry().getValue()) {
exceptionHandler.push(n);
return true;
}
return false;
case Token.TRY:
exceptionHandler.push(n);
return true;
}
/*
* We are going to stop the traversal depending on what the node's parent
* is.
*
* We are only interested in adding edges between nodes that change control
* flow. The most obvious ones are loops and IF-ELSE's. A statement
* transfers control to its next sibling.
*
* In case of an expression tree, there is no control flow within the tree
* even when there are short circuited operators and conditionals. When we
* are doing data flow analysis, we will simply synthesize lattices up the
* expression tree by finding the meet at each expression node.
*
* For example: within a Token.SWITCH, the expression in question does not
* change the control flow and need not to be considered.
*/
if (parent != null) {
switch (parent.getType()) {
case Token.FOR:
// Only traverse the body of the for loop.
return n == parent.getLastChild();
// Skip the conditions.
case Token.IF:
case Token.WHILE:
case Token.WITH:
return n != parent.getFirstChild();
case Token.DO:
return n != parent.getFirstChild().getNext();
//
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> Only traverse the body of the cases
case Token.SWITCH:
case Token.CASE:
case Token.CATCH:
case Token.LABEL:
return n != parent.getFirstChild();
case Token.FUNCTION:
return n == parent.getFirstChild().getNext().getNext();
case Token.CONTINUE:
case Token.BREAK:
case Token.EXPR_RESULT:
case Token.VAR:
case Token.RETURN:
case Token.THROW:
return false;
case Token.TRY:
/* Just before we are about to visit the second child of the TRY node,
* we know that we will be visiting either the CATCH or the FINALLY.
* In other words, we know that the post order traversal of the TRY
* block has been finished, no more exceptions can be caught by the
* handler at this TRY block and should be taken out of the stack.
*/
if (n == parent.getFirstChild().getNext()) {
Preconditions.checkState(exceptionHandler.peek() == parent);
exceptionHandler.pop();
}
}
}
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.IF:
handleIf(n);
return;
case Token.WHILE:
handleWhile(n);
return;
case Token.DO:
handleDo(n);
return;
case Token.FOR:
handleFor(n);
return;
case Token.SWITCH:
handleSwitch(n);
return;
case Token.CASE:
handleCase(n);
return;
case Token.DEFAULT_CASE:
handleDefault(n);
return;
case Token.BLOCK:
case Token.SCRIPT:
handleStmtList(n);
return;
case Token.FUNCTION:
handleFunction(n);
return;
case Token.EXPR_RESULT:
handleExpr(n);
return;
case Token.THROW:
handleThrow(n);
return;
case Token.TRY:
handleTry(n);
return;
case Token.CATCH:
handleCatch(n);
return;
case Token.BREAK:
handleBreak(n);
return;
case Token.CONTINUE:
handleContinue(n);
return;
case Token.RETURN:
handleReturn(n);
return;
case Token.WITH:
handleWith(n);
return;
case Token.LABEL:
return;
default:
handleStmt(n);
return;
}
}
private void handleIf(Node node) {
Node thenBlock = node.getFirstChild().getNext();
Node elseBlock = thenBlock.getNext();
createEdge(node, Branch.ON_TRUE, computeFallThrough(thenBlock));
if (elseBlock == null) {
createEdge(node, Branch.ON_FALSE,
computeFollowNode(node, this)); // not taken branch
} else {
createEdge(node, Branch.ON_FALSE, computeFallThrough(elseBlock));
}
connectToPossibleExceptionHandler(
node, NodeUtil.getConditionExpression(node));
}
private void handleWhile(Node node) {
// Control goes to the first statement if the condition evaluates to true.
createEdge(node, Branch.ON_
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>UNCOND, node.getFirstChild().getNext());
} else { // No CASE, no DEFAULT
createEdge(node, Branch.UNCOND, computeFollowNode(node, this));
}
}
connectToPossibleExceptionHandler(node, node.getFirstChild());
}
private void handleCase(Node node) {
// Case is a bit tricky....First it goes into the body if condition is true.
createEdge(node, Branch.ON_TRUE,
node.getFirstChild().getNext());
// Look for the next CASE, skipping over DEFAULT.
Node next = getNextSiblingOfType(node.getNext(), Token.CASE);
if (next != null) { // Found a CASE
Preconditions.checkState(next.isCase());
createEdge(node, Branch.ON_FALSE, next);
} else { // No more CASE found, go back and search for a DEFAULT.
Node parent = node.getParent();
Node deflt = getNextSiblingOfType(
parent.getFirstChild().getNext(), Token.DEFAULT_CASE);
if (deflt != null) { // Has a DEFAULT
createEdge(node, Branch.ON_FALSE, deflt);
} else { // No DEFAULT found, go to the follow of the SWITCH.
createEdge(node, Branch.ON_FALSE, computeFollowNode(node, this));
}
}
connectToPossibleExceptionHandler(node, node.getFirstChild());
}
private void handleDefault(Node node) {
// Directly goes to the body. It should not transfer to the next case.
createEdge(node, Branch.UNCOND, node.getFirstChild());
}
private void handleWith(Node node) {
// Directly goes to the body. It should not transfer to the next case.
createEdge(node, Branch.UNCOND, node.getLastChild());
connectToPossibleExceptionHandler(node, node.getFirstChild());
}
private void handleStmtList(Node node) {
Node parent = node.getParent();
// Special case, don't add a block of empty CATCH block to the graph.
if (node.isBlock() && parent != null &&
parent.isTry() &&
NodeUtil.getCatchBlock(parent) == node &&
!NodeUtil.hasCatchHandler(node)) {
return;
}
// A block transfer control to its first child if it is not empty.
Node child = node.getFirstChild();
// Function declarations are skipped since control doesn't go into that
// function (unless it is called)
while (child != null && child.isFunction()) {
child = child.getNext();
}
if (child != null) {
createEdge(node, Branch.UNCOND, computeFallThrough(child));
} else {
createEdge(node, Branch.UNCOND, computeFollowNode(node, this));
}
// Synthetic blocks
if (parent != null) {
switch (parent.getType()) {
case Token.DEFAULT_CASE:
case Token.CASE:
case Token.TRY:
break;
default:
if (node.isBlock() && node.isSyntheticBlock()) {
createEdge(node, Branch.SYN_BLOCK, computeFollowNode(node, this));
}
break;
}
}
}
private void handleFunction(Node node) {
//
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> return statement, we should also transfer control
* back to the caller of the function.
*
* 4. If the node is root then we have reached the end of what we have been
* asked to traverse.
*
* In all cases we should transfer control to a "symbolic return" node.
* This will make life easier for DFAs.
*/
Node parent = node.getParent();
if (parent == null || parent.isFunction() ||
(cfa != null && node == cfa.root)) {
return null;
}
// If we are just before a IF/WHILE/DO/FOR:
switch (parent.getType()) {
// The follow() of any of the path from IF would be what follows IF.
case Token.IF:
return computeFollowNode(fromNode, parent, cfa);
case Token.CASE:
case Token.DEFAULT_CASE:
// After the body of a CASE, the control goes to the body of the next
// case, without having to go to the case condition.
if (parent.getNext() != null) {
if (parent.getNext().isCase()) {
return parent.getNext().getFirstChild().getNext();
} else if (parent.getNext().isDefaultCase()) {
return parent.getNext().getFirstChild();
} else {
Preconditions.checkState(false, "Not reachable");
}
} else {
return computeFollowNode(fromNode, parent, cfa);
}
break;
case Token.FOR:
if (NodeUtil.isForIn(parent)) {
return parent;
} else {
return parent.getFirstChild().getNext().getNext();
}
case Token.WHILE:
case Token.DO:
return parent;
case Token.TRY:
// If we are coming out of the TRY block...
if (parent.getFirstChild() == node) {
if (NodeUtil.hasFinally(parent)) { // and have FINALLY block.
return computeFallThrough(parent.getLastChild());
} else { // and have no FINALLY.
return computeFollowNode(fromNode, parent, cfa);
}
// CATCH block.
} else if (NodeUtil.getCatchBlock(parent) == node){
if (NodeUtil.hasFinally(parent)) { // and have FINALLY block.
return computeFallThrough(node.getNext());
} else {
return computeFollowNode(fromNode, parent, cfa);
}
// If we are coming out of the FINALLY block...
} else if (parent.getLastChild() == node){
if (cfa != null) {
for (Node finallyNode : cfa.finallyMap.get(parent)) {
cfa.createEdge(fromNode, Branch.ON_EX, finallyNode);
}
}
return computeFollowNode(fromNode, parent, cfa);
}
}
// Now that we are done with the special cases follow should be its
// immediate sibling, unless its sibling is a function
Node nextSibling = node.getNext();
// Skip function declarations because control doesn't get pass into it.
while (nextSibling != null && nextSibling.isFunction()) {
nextSibling = nextSibling.getNext();
}
if (nextSibling != null) {
return computeFallThrough(
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>nextSibling);
} else {
// If there are no more siblings, control is transferred up the AST.
return computeFollowNode(fromNode, parent, cfa);
}
}
/**
* Computes the destination node of n when we want to fallthrough into the
* subtree of n. We don't always create a CFG edge into n itself because of
* DOs and FORs.
*/
static Node computeFallThrough(Node n) {
switch (n.getType()) {
case Token.DO:
return computeFallThrough(n.getFirstChild());
case Token.FOR:
if (NodeUtil.isForIn(n)) {
return n.getFirstChild().getNext();
}
return computeFallThrough(n.getFirstChild());
case Token.LABEL:
return computeFallThrough(n.getLastChild());
default:
return n;
}
}
/**
* Connects the two nodes in the control flow graph.
*
* @param fromNode Source.
* @param toNode Destination.
*/
private void createEdge(Node fromNode, ControlFlowGraph.Branch branch,
Node toNode) {
cfg.createNode(fromNode);
cfg.createNode(toNode);
cfg.connectIfNotFound(fromNode, branch, toNode);
}
/**
* Connects cfgNode to the proper CATCH block if target subtree might throw
* an exception. If there are FINALLY blocks reached before a CATCH, it will
* make the corresponding entry in finallyMap.
*/
private void connectToPossibleExceptionHandler(Node cfgNode, Node target) {
if (mayThrowException(target) && !exceptionHandler.isEmpty()) {
Node lastJump = cfgNode;
for (Node handler : exceptionHandler) {
if (handler.isFunction()) {
return;
}
Preconditions.checkState(handler.isTry());
Node catchBlock = NodeUtil.getCatchBlock(handler);
if (!NodeUtil.hasCatchHandler(catchBlock)) { // No catch but a FINALLY.
if (lastJump == cfgNode) {
createEdge(cfgNode, Branch.ON_EX, handler.getLastChild());
} else {
finallyMap.put(lastJump, handler.getLastChild());
}
} else { // Has a catch.
if (lastJump == cfgNode) {
createEdge(cfgNode, Branch.ON_EX, catchBlock);
return;
} else {
finallyMap.put(lastJump, catchBlock);
}
}
lastJump = handler;
}
}
}
/**
* Get the next sibling (including itself) of one of the given types.
*/
private static Node getNextSiblingOfType(Node first, int ... types) {
for (Node c = first; c != null; c = c.getNext()) {
for (int type : types) {
if (c.getType() == type) {
return c;
}
}
}
return null;
}
/**
* Checks if target is actually the break target of labeled continue. The
* label can be null if it is an unlabeled break.
*/
public static boolean isBreakTarget(Node target, String label) {
return isBreakStructure(target, label != null) &&
matchLabel(target.getParent(),
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> label);
}
/**
* Checks if target is actually the continue target of labeled continue. The
* label can be null if it is an unlabeled continue.
*/
private static boolean isContinueTarget(
Node target, Node parent, String label) {
return isContinueStructure(target) && matchLabel(parent, label);
}
/**
* Check if label is actually referencing the target control structure. If
* label is null, it always returns true.
*/
private static boolean matchLabel(Node target, String label) {
if (label == null) {
return true;
}
while (target.isLabel()) {
if (target.getFirstChild().getString().equals(label)) {
return true;
}
target = target.getParent();
}
return false;
}
/**
* Determines if the subtree might throw an exception.
*/
public static boolean mayThrowException(Node n) {
switch (n.getType()) {
case Token.CALL:
case Token.GETPROP:
case Token.GETELEM:
case Token.THROW:
case Token.NEW:
case Token.ASSIGN:
case Token.INC:
case Token.DEC:
case Token.INSTANCEOF:
return true;
case Token.FUNCTION:
return false;
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(c) && mayThrowException(c)) {
return true;
}
}
return false;
}
/**
* Determines whether the given node can be terminated with a BREAK node.
*/
static boolean isBreakStructure(Node n, boolean labeled) {
switch (n.getType()) {
case Token.FOR:
case Token.DO:
case Token.WHILE:
case Token.SWITCH:
return true;
case Token.BLOCK:
case Token.IF:
case Token.TRY:
return labeled;
default:
return false;
}
}
/**
* Determines whether the given node can be advanced with a CONTINUE node.
*/
static boolean isContinueStructure(Node n) {
switch (n.getType()) {
case Token.FOR:
case Token.DO:
case Token.WHILE:
return true;
default:
return false;
}
}
/**
* Get the TRY block with a CATCH that would be run if n throws an exception.
* @return The CATCH node or null if it there isn't a CATCH before the
* the function terminates.
*/
static Node getExceptionHandler(Node n) {
for (Node cur = n;
!cur.isScript() && !cur.isFunction();
cur = cur.getParent()) {
Node catchNode = getCatchHandlerForBlock(cur);
if (catchNode != null) {
return catchNode;
}
}
return null;
}
/**
* Locate the catch BLOCK given the first block in a TRY.
* @return The CATCH node or null there is no catch handler.
*/
static Node getCatchHandlerForBlock(Node block) {
if (block.isBlock() &&
block.getParent().isTry() &&
block.getParent().getFirstChild() == block)
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>, true);
ObjectType GLOBAL_THIS = GLOBAL_THIS_CTOR.getInstanceType();
registerNativeType(JSTypeNative.GLOBAL_THIS, GLOBAL_THIS);
// greatest function type, i.e. (NoType...) -> All
FunctionType GREATEST_FUNCTION_TYPE =
createNativeFunctionTypeWithVarArgs(ALL_TYPE, NO_TYPE);
registerNativeType(JSTypeNative.GREATEST_FUNCTION_TYPE,
GREATEST_FUNCTION_TYPE);
// Register the prototype property. See the comments below in
// registerPropertyOnType about the bootstrapping process.
registerPropertyOnType("prototype", OBJECT_FUNCTION_TYPE);
}
private void initializeRegistry() {
register(getNativeType(JSTypeNative.ARRAY_TYPE));
register(getNativeType(JSTypeNative.BOOLEAN_OBJECT_TYPE));
register(getNativeType(JSTypeNative.BOOLEAN_TYPE));
register(getNativeType(JSTypeNative.DATE_TYPE));
register(getNativeType(JSTypeNative.NULL_TYPE));
register(getNativeType(JSTypeNative.NULL_TYPE), "Null");
register(getNativeType(JSTypeNative.NUMBER_OBJECT_TYPE));
register(getNativeType(JSTypeNative.NUMBER_TYPE));
register(getNativeType(JSTypeNative.OBJECT_TYPE));
register(getNativeType(JSTypeNative.ERROR_TYPE));
register(getNativeType(JSTypeNative.URI_ERROR_TYPE));
register(getNativeType(JSTypeNative.EVAL_ERROR_TYPE));
register(getNativeType(JSTypeNative.TYPE_ERROR_TYPE));
register(getNativeType(JSTypeNative.RANGE_ERROR_TYPE));
register(getNativeType(JSTypeNative.REFERENCE_ERROR_TYPE));
register(getNativeType(JSTypeNative.SYNTAX_ERROR_TYPE));
register(getNativeType(JSTypeNative.REGEXP_TYPE));
register(getNativeType(JSTypeNative.STRING_OBJECT_TYPE));
register(getNativeType(JSTypeNative.STRING_TYPE));
register(getNativeType(JSTypeNative.VOID_TYPE));
register(getNativeType(JSTypeNative.VOID_TYPE), "Undefined");
register(getNativeType(JSTypeNative.VOID_TYPE), "void");
register(getNativeType(JSTypeNative.FUNCTION_INSTANCE_TYPE), "Function");
}
private void register(JSType type) {
register(type, type.toString());
}
private void register(JSType type, String name) {
Preconditions.checkArgument(
!name.contains("<"), "Type names cannot contain template annotations.");
namesToTypes.put(name, type);
// Add all the namespaces in which this name lives.
while (name.indexOf('.') > 0) {
name = name.substring(0, name.lastIndexOf('.'));
namespaces.add(name);
}
}
private void registerNativeType(JSTypeNative typeId, JSType type) {
nativeTypes[typeId.ordinal()] = type;
}
/**
* Tells the type system that {@code owner} may have a property named
* {@code propertyName}. This allows the registry to keep track of what
* types a property is defined
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>contains(name);
}
/**
* Looks up a type by name.
*
* @param jsTypeName The name string.
* @return the corresponding JSType object or {@code null} it cannot be found
*/
public JSType getType(String jsTypeName) {
// TODO(user): Push every local type name out of namesToTypes so that
// NamedType#resolve is correct.
TemplateType templateType = templateTypes.get(jsTypeName);
if (templateType != null) {
return templateType;
}
return namesToTypes.get(jsTypeName);
}
public JSType getNativeType(JSTypeNative typeId) {
return nativeTypes[typeId.ordinal()];
}
public ObjectType getNativeObjectType(JSTypeNative typeId) {
return (ObjectType) getNativeType(typeId);
}
public FunctionType getNativeFunctionType(JSTypeNative typeId) {
return (FunctionType) getNativeType(typeId);
}
/**
* Looks up a type by name. To allow for forward references to types, an
* unrecognized string has to be bound to a NamedType object that will be
* resolved later.
*
* @param scope A scope for doing type name resolution.
* @param jsTypeName The name string.
* @param sourceName The name of the source file where this reference appears.
* @param lineno The line number of the reference.
* @return a NamedType if the string argument is not one of the known types,
* otherwise the corresponding JSType object.
*/
public JSType getType(StaticScope<JSType> scope, String jsTypeName,
String sourceName, int lineno, int charno) {
// Resolve template type names
JSType type = null;
JSType thisType = null;
if (scope != null && scope.getTypeOfThis() != null) {
thisType = scope.getTypeOfThis().toObjectType();
}
if (thisType != null) {
type = thisType.getTemplateTypeMap().getTemplateTypeKeyByName(jsTypeName);
if (type != null) {
Preconditions.checkState(type.isTemplateType(), "expected:" + type);
return type;
}
}
type = getType(jsTypeName);
if (type == null) {
// TODO(user): Each instance should support named type creation using
// interning.
NamedType namedType =
new NamedType(this, jsTypeName, sourceName, lineno, charno);
unresolvedNamedTypes.put(scope, namedType);
type = namedType;
}
return type;
}
/**
* Flushes out the current resolved and unresolved Named Types from
* the type registry. This is intended to be used ONLY before a
* compile is run.
*/
public void clearNamedTypes() {
resolvedNamedTypes.clear();
unresolvedNamedTypes.clear();
}
/**
* Resolve all the unresolved types in the given scope.
*/
public void resolveTypesInScope(StaticScope<JSType> scope) {
for (NamedType type : unresolvedNamedTypes.get(scope)) {
type.resolve(reporter, scope);
}
resolvedNamedTypes.putAll(scope, unresolvedNamedTypes.removeAll(scope));
if (scope
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> != null && scope.getParentScope() == null) {
// By default, the global "this" type is just an anonymous object.
// If the user has defined a Window type, make the Window the
// implicit prototype of "this".
PrototypeObjectType globalThis = (PrototypeObjectType) getNativeType(
JSTypeNative.GLOBAL_THIS);
JSType windowType = getType("Window");
if (globalThis.isUnknownType()) {
ObjectType windowObjType = ObjectType.cast(windowType);
if (windowObjType != null) {
globalThis.setImplicitPrototype(windowObjType);
} else {
globalThis.setImplicitPrototype(
getNativeObjectType(JSTypeNative.OBJECT_TYPE));
}
}
}
}
/**
* Creates a type representing optional values of the given type.
* @return the union of the type and the void type
*/
public JSType createOptionalType(JSType type) {
if (type instanceof UnknownType || type.isAllType()) {
return type;
} else {
return createUnionType(type, getNativeType(JSTypeNative.VOID_TYPE));
}
}
/**
* Creates a type representing nullable values of the given type.
* @return the union of the type and the Null type
*/
public JSType createDefaultObjectUnion(JSType type) {
if (type.isTemplateType()) {
// Template types represent the substituted type exactly and should
// not be wrapped.
return type;
} else {
return shouldTolerateUndefinedValues()
? createOptionalNullableType(type)
: createNullableType(type);
}
}
/**
* Creates a type representing nullable values of the given type.
* @return the union of the type and the Null type
*/
public JSType createNullableType(JSType type) {
return createUnionType(type, getNativeType(JSTypeNative.NULL_TYPE));
}
/**
* Creates a nullable and undefine-able value of the given type.
* @return The union of the type and null and undefined.
*/
public JSType createOptionalNullableType(JSType type) {
return createUnionType(type, getNativeType(JSTypeNative.VOID_TYPE),
getNativeType(JSTypeNative.NULL_TYPE));
}
/**
* Creates a union type whose variants are the arguments.
*/
public JSType createUnionType(JSType... variants) {
UnionTypeBuilder builder = new UnionTypeBuilder(this);
for (JSType type : variants) {
builder.addAlternate(type);
}
return builder.build();
}
/**
* Creates a union type whose variants are the built-in types specified
* by the arguments.
*/
public JSType createUnionType(JSTypeNative... variants) {
UnionTypeBuilder builder = new UnionTypeBuilder(this);
for (JSTypeNative typeId : variants) {
builder.addAlternate(getNativeType(typeId));
}
return builder.build();
}
/**
* Creates an enum type.
*/
public EnumType createEnumType(
String name, Node source, JSType elementsType) {
return new EnumType(this, name, source, elementsType);
}
/**
* Creates
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>getUnfilledTemplateKeys()) {
JSType templatizedType = templatizedTypes.containsKey(key) ?
templatizedTypes.get(key) : getNativeType(UNKNOWN_TYPE);
builder.add(templatizedType);
}
return createTemplatizedType(baseType, builder.build());
}
/**
* Creates a templatized instance of the specified type. Only ObjectTypes
* can currently be templatized; extend the logic in this function when
* more types can be templatized.
* @param baseType the type to be templatized.
* @param templatizedTypes a list of the template JSTypes. Will be matched by
* list order to the template keys on the base type.
*/
public TemplatizedType createTemplatizedType(
ObjectType baseType, JSType... templatizedTypes) {
return createTemplatizedType(
baseType, ImmutableList.copyOf(templatizedTypes));
}
/**
* Creates a named type.
*/
@VisibleForTesting
public JSType createNamedType(String reference,
String sourceName, int lineno, int charno) {
return new NamedType(this, reference, sourceName, lineno, charno);
}
/**
* Identifies the name of a typedef or enum before we actually declare it.
*/
public void identifyNonNullableName(String name) {
Preconditions.checkNotNull(name);
nonNullableTypeNames.add(name);
}
/**
* Creates a JSType from the nodes representing a type.
* @param n The node with type info.
* @param sourceName The source file name.
* @param scope A scope for doing type name lookups.
*/
public JSType createFromTypeNodes(Node n, String sourceName,
StaticScope<JSType> scope) {
if (resolveMode == ResolveMode.LAZY_EXPRESSIONS) {
// If the type expression doesn't contain any names, just
// resolve it anyway.
boolean hasNames = hasTypeName(n);
if (hasNames) {
return new UnresolvedTypeExpression(this, n, sourceName);
}
}
return createFromTypeNodesInternal(n, sourceName, scope);
}
private boolean hasTypeName(Node n) {
if (n.getType() == Token.STRING) {
return true;
}
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (hasTypeName(child)) {
return true;
}
}
return false;
}
/** @see #createFromTypeNodes(Node, String, StaticScope) */
private JSType createFromTypeNodesInternal(Node n, String sourceName,
StaticScope<JSType> scope) {
switch (n.getType()) {
case Token.LC: // Record type.
return createRecordTypeFromNodes(
n.getFirstChild(), sourceName, scope);
case Token.BANG: // Not nullable
return createFromTypeNodesInternal(
n.getFirstChild(), sourceName, scope)
.restrictByNotNullOrUndefined();
case Token.QMARK: // Nullable or unknown
Node firstChild = n.getFirstChild();
if (firstChild == null) {
return getNativeType(
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>UNKNOWN_TYPE);
}
return createDefaultObjectUnion(
createFromTypeNodesInternal(
firstChild, sourceName, scope));
case Token.EQUALS: // Optional
return createOptionalType(
createFromTypeNodesInternal(
n.getFirstChild(), sourceName, scope));
case Token.ELLIPSIS: // Var args
return createOptionalType(
createFromTypeNodesInternal(
n.getFirstChild(), sourceName, scope));
case Token.STAR: // The AllType
return getNativeType(ALL_TYPE);
case Token.LB: // Array type
// TODO(nicksantos): Enforce membership restrictions on the Array.
return getNativeType(ARRAY_TYPE);
case Token.PIPE: // Union type
UnionTypeBuilder builder = new UnionTypeBuilder(this);
for (Node child = n.getFirstChild(); child != null;
child = child.getNext()) {
builder.addAlternate(
createFromTypeNodesInternal(child, sourceName, scope));
}
return builder.build();
case Token.EMPTY: // When the return value of a function is not specified
return getNativeType(UNKNOWN_TYPE);
case Token.VOID: // Only allowed in the return value of a function.
return getNativeType(VOID_TYPE);
case Token.STRING:
JSType namedType = getType(scope, n.getString(), sourceName,
n.getLineno(), n.getCharno());
if (resolveMode != ResolveMode.LAZY_NAMES) {
namedType = namedType.resolveInternal(reporter, scope);
}
if ((namedType instanceof ObjectType) &&
!(nonNullableTypeNames.contains(n.getString()))) {
Node typeList = n.getFirstChild();
int nAllowedTypes =
namedType.getTemplateTypeMap().numUnfilledTemplateKeys();
if (typeList != null && nAllowedTypes > 0) {
// Templatized types.
ImmutableList.Builder<JSType> templateTypes =
ImmutableList.builder();
// Special case for Object, where Object.<X> implies Object.<?,X>.
if (n.getString().equals("Object") &&
typeList.getFirstChild() == typeList.getLastChild()) {
templateTypes.add(getNativeType(UNKNOWN_TYPE));
}
int templateNodeIndex = 0;
for (Node templateNode : typeList.getFirstChild().siblings()) {
// Don't parse more templatized type nodes than the type can
// accommodate. This is because some existing clients have
// template annotations on non-templatized classes, for instance:
// goog.structs.Set.<SomeType>
// The problem in these cases is that the previously-unparsed
// SomeType is not actually a valid type. To prevent these clients
// from seeing unknown type errors, we explicitly don't parse
// these types.
// TODO(user): Address this issue by removing bad template
// annotations on non-templatized classes.
if (++templateNodeIndex > nAllowedTypes) {
break;
}
templateTypes.add(createFromTypeNodesInternal(
templateNode, sourceName, scope));
}
namedType = createTemplatizedType(
(ObjectType) namedType, templateTypes.build());
Preconditions.checkNotNull(namedType);
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>
return createDefaultObjectUnion(namedType);
} else {
return namedType;
}
case Token.FUNCTION:
ObjectType thisType = null;
boolean isConstructor = false;
Node current = n.getFirstChild();
if (current.getType() == Token.THIS ||
current.getType() == Token.NEW) {
Node contextNode = current.getFirstChild();
thisType =
ObjectType.cast(
createFromTypeNodesInternal(
contextNode, sourceName, scope)
.restrictByNotNullOrUndefined());
if (thisType == null) {
reporter.warning(
SimpleErrorReporter.getMessage0(
current.getType() == Token.THIS ?
"msg.jsdoc.function.thisnotobject" :
"msg.jsdoc.function.newnotobject"),
sourceName,
contextNode.getLineno(), contextNode.getCharno());
}
isConstructor = current.getType() == Token.NEW;
current = current.getNext();
}
FunctionParamBuilder paramBuilder = new FunctionParamBuilder(this);
if (current.getType() == Token.PARAM_LIST) {
for (Node arg = current.getFirstChild(); arg != null;
arg = arg.getNext()) {
if (arg.getType() == Token.ELLIPSIS) {
if (arg.getChildCount() == 0) {
paramBuilder.addVarArgs(getNativeType(UNKNOWN_TYPE));
} else {
paramBuilder.addVarArgs(
createFromTypeNodesInternal(
arg.getFirstChild(), sourceName, scope));
}
} else {
JSType type = createFromTypeNodesInternal(
arg, sourceName, scope);
if (arg.getType() == Token.EQUALS) {
boolean addSuccess = paramBuilder.addOptionalParams(type);
if (!addSuccess) {
reporter.warning(
SimpleErrorReporter.getMessage0(
"msg.jsdoc.function.varargs"),
sourceName, arg.getLineno(), arg.getCharno());
}
} else {
paramBuilder.addRequiredParams(type);
}
}
}
current = current.getNext();
}
JSType returnType =
createFromTypeNodesInternal(current, sourceName, scope);
return new FunctionBuilder(this)
.withParams(paramBuilder)
.withReturnType(returnType)
.withTypeOfThis(thisType)
.setIsConstructor(isConstructor)
.build();
}
throw new IllegalStateException(
"Unexpected node in type expression: " + n.toString());
}
/**
* Creates a RecordType from the nodes representing said record type.
* @param n The node with type info.
* @param sourceName The source file name.
* @param scope A scope for doing type name lookups.
*/
private JSType createRecordTypeFromNodes(Node n, String sourceName,
StaticScope<JSType> scope) {
RecordTypeBuilder builder = new RecordTypeBuilder(this);
// For each of the fields in the record type.
for (Node fieldTypeNode = n.getFirstChild();
fieldTypeNode != null;
fieldTypeNode = fieldTypeNode.getNext()) {
// Get the property's name.
Node fieldNameNode = fieldTypeNode;
boolean hasType = false;
if (
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>fieldTypeNode.getType() == Token.COLON) {
fieldNameNode = fieldTypeNode.getFirstChild();
hasType = true;
}
String fieldName = fieldNameNode.getString();
// TODO(user): Move this into the lexer/parser.
// Remove the string literal characters around a field name,
// if any.
if (fieldName.startsWith("'") || fieldName.startsWith("\"")) {
fieldName = fieldName.substring(1, fieldName.length() - 1);
}
// Get the property's type.
JSType fieldType = null;
if (hasType) {
// We have a declared type.
fieldType = createFromTypeNodesInternal(
fieldTypeNode.getLastChild(), sourceName, scope);
} else {
// Otherwise, the type is UNKNOWN.
fieldType = getNativeType(JSTypeNative.UNKNOWN_TYPE);
}
// Add the property to the record.
if (builder.addProperty(fieldName, fieldType, fieldNameNode) == null) {
// Duplicate field name, warning and skip
reporter.warning(
"Duplicate record field " + fieldName,
sourceName,
n.getLineno(), fieldNameNode.getCharno());
}
}
return builder.build();
}
/**
* Sets the template type name.
*/
public void setTemplateTypeNames(List<TemplateType> keys) {
Preconditions.checkNotNull(keys);
for (TemplateType key : keys) {
templateTypes.put(key.getReferenceName(), key);
}
}
/**
* Clears the template type name.
*/
public void clearTemplateTypeNames() {
templateTypes.clear();
}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>/*
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Rhino code, released
* May 6, 1999.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1997-1999
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Bob Jervis
* Google Inc.
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU General Public License Version 2 or later (the "GPL"), in which
* case the provisions of the GPL are applicable instead of those above. If
* you wish to allow use of your version of this file only under the terms of
* the GPL and not to allow others to use your version of this file under the
* MPL, indicate your decision by deleting the provisions above and replacing
* them with the notice and other provisions required by the GPL. If you do
* not delete the provisions above, a recipient may use your version of this
* file under either the MPL or the GPL.
*
* ***** END LICENSE BLOCK ***** */
package com.google.javascript.rhino.jstype;
import com.google.javascript.rhino.JSDocInfo;
import java.io.Serializable;
/**
* The minimum implementation of StaticSlot<JSType>.
*
* @author nicksantos@google.com (Nick Santos)
*/
public class SimpleSlot implements StaticSlot<JSType>, Serializable {
private static final long serialVersionUID = 1L;
final String name;
final JSType type;
final boolean inferred;
public SimpleSlot(String name, JSType type, boolean inferred) {
this.name = name;
this.type = type;
this.inferred = inferred;
}
@Override
public String getName() {
return name;
}
@Override
public JSType getType() {
return type;
}
@Override
public boolean isTypeInferred() {
return inferred;
}
@Override
public StaticReference<JSType> getDeclaration() {
return null;
}
@Override
public JSDocInfo getJSDocInfo() {
return null;
}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> recorded
*/
public boolean recordNoTypeCheck() {
if (!currentInfo.isNoTypeCheck()) {
currentInfo.setNoCheck(true);
populated = true;
return true;
} else {
return false;
}
}
/**
* Records that the {@link JSDocInfo} being built should have its
* {@link JSDocInfo#isConstructor()} flag set to {@code true}.
*
* @return {@code true} if the constructor was recorded and {@code false}
* if it was already defined or it was incompatible with the existing
* flags
*/
public boolean recordConstructor() {
if (!hasAnySingletonTypeTags() &&
!currentInfo.isConstructor() && !currentInfo.isInterface()) {
currentInfo.setConstructor(true);
populated = true;
return true;
} else {
return false;
}
}
/**
* Whether the {@link JSDocInfo} being built will have its
* {@link JSDocInfo#isConstructor()} flag set to {@code true}.
*/
public boolean isConstructorRecorded() {
return currentInfo.isConstructor();
}
/**
* Records that the {@link JSDocInfo} being built should have its
* {@link JSDocInfo#makesStructs()} flag set to {@code true}.
*
* @return {@code true} if the struct was recorded and {@code false}
* if it was already defined or it was incompatible with the existing flags
*/
public boolean recordStruct() {
if (hasAnySingletonTypeTags() || currentInfo.isInterface() ||
currentInfo.makesDicts() || currentInfo.makesStructs()) {
return false;
}
currentInfo.setStruct();
populated = true;
return true;
}
/**
* Records that the {@link JSDocInfo} being built should have its
* {@link JSDocInfo#makesDicts()} flag set to {@code true}.
*
* @return {@code true} if the dict was recorded and {@code false}
* if it was already defined or it was incompatible with the existing flags
*/
public boolean recordDict() {
if (hasAnySingletonTypeTags() || currentInfo.isInterface() ||
currentInfo.makesDicts() || currentInfo.makesStructs()) {
return false;
}
currentInfo.setDict();
populated = true;
return true;
}
/**
* Records that the {@link JSDocInfo} being built should have its
* {@link JSDocInfo#isJavaDispatch()} flag set to {@code true}.
*
* @return {@code true} if the javadispatch was recorded and {@code false}
* if it was already defined or it was incompatible with the existing
* flags
*/
public boolean recordJavaDispatch() {
if (!currentInfo.isJavaDispatch()) {
currentInfo.setJavaDispatch(true);
populated = true;
return true;
} else {
return false;
}
}
/**
* Whether the {@link JSDocInfo} being built will have its
* {@link JSDocInfo#isJavaDispatch()} flag set to {@code true}.
*/
public boolean isJavaDispatch
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>() {
return currentInfo.isJavaDispatch();
}
/**
* Records that the {@link JSDocInfo} being built should have its
* {@link JSDocInfo#shouldPreserveTry()} flag set to {@code true}.
*/
public boolean recordPreserveTry() {
if (!currentInfo.shouldPreserveTry()) {
currentInfo.setShouldPreserveTry(true);
populated = true;
return true;
} else {
return false;
}
}
/**
* Records that the {@link JSDocInfo} being built should have its
* {@link JSDocInfo#isOverride()} flag set to {@code true}.
*/
public boolean recordOverride() {
if (!currentInfo.isOverride()) {
currentInfo.setOverride(true);
populated = true;
return true;
} else {
return false;
}
}
/**
* Records that the {@link JSDocInfo} being built should have its
* {@link JSDocInfo#isNoAlias()} flag set to {@code true}.
*/
public boolean recordNoAlias() {
if (!currentInfo.isNoAlias()) {
currentInfo.setNoAlias(true);
populated = true;
return true;
} else {
return false;
}
}
/**
* Records that the {@link JSDocInfo} being built should have its
* {@link JSDocInfo#isDeprecated()} flag set to {@code true}.
*/
public boolean recordDeprecated() {
if (!currentInfo.isDeprecated()) {
currentInfo.setDeprecated(true);
populated = true;
return true;
} else {
return false;
}
}
/**
* Records that the {@link JSDocInfo} being built should have its
* {@link JSDocInfo#isInterface()} flag set to {@code true}.
*
* @return {@code true} if the flag was recorded and {@code false}
* if it was already defined or it was incompatible with the existing flags
*/
public boolean recordInterface() {
if (hasAnySingletonTypeTags() ||
currentInfo.makesStructs() || currentInfo.makesDicts() ||
currentInfo.isConstructor() || currentInfo.isInterface()) {
return false;
}
currentInfo.setInterface(true);
populated = true;
return true;
}
/**
* Records that the {@link JSDocInfo} being built should have its
* {@link JSDocInfo#isExport()} flag set to {@code true}.
*/
public boolean recordExport() {
if (!currentInfo.isExport()) {
currentInfo.setExport(true);
populated = true;
return true;
} else {
return false;
}
}
/**
* Records that the {@link JSDocInfo} being built should have its
* {@link JSDocInfo#isExpose()} flag set to {@code true}.
*/
public boolean recordExpose() {
if (!currentInfo.isExpose()) {
currentInfo.setExpose(true);
populated = true;
return true;
} else {
return false;
}
}
/**
* Records that the {@link JSDocInfo} being built should have its
* {@link JSDocInfo
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>#isNoShadow()} flag set to {@code true}.
*/
public boolean recordNoShadow() {
if (!currentInfo.isNoShadow()) {
currentInfo.setNoShadow(true);
populated = true;
return true;
} else {
return false;
}
}
/**
* Records that the {@link JSDocInfo} being built should have its
* {@link JSDocInfo#isImplicitCast()} flag set to {@code true}.
*/
public boolean recordImplicitCast() {
if (!currentInfo.isImplicitCast()) {
currentInfo.setImplicitCast(true);
populated = true;
return true;
} else {
return false;
}
}
/**
* Records that the {@link JSDocInfo} being built should have its
* {@link JSDocInfo#isNoSideEffects()} flag set to {@code true}.
*/
public boolean recordNoSideEffects() {
if (!hasAnySingletonSideEffectTags()
&& !currentInfo.isNoSideEffects()) {
currentInfo.setNoSideEffects(true);
populated = true;
return true;
} else {
return false;
}
}
/**
* Records that the {@link JSDocInfo} being built should have its
* {@link JSDocInfo#isExterns()} flag set to {@code true}.
*/
public boolean recordExterns() {
if (!currentInfo.isExterns()) {
currentInfo.setExterns(true);
populated = true;
return true;
} else {
return false;
}
}
/**
* Whether the {@link JSDocInfo} being built will have its
* {@link JSDocInfo#isInterface()} flag set to {@code true}.
*/
public boolean isInterfaceRecorded() {
return currentInfo.isInterface();
}
/**
* @return Whether a parameter of the given name has already been recorded.
*/
public boolean hasParameter(String name) {
return currentInfo.hasParameter(name);
}
/**
* Records an implemented interface.
*/
public boolean recordImplementedInterface(JSTypeExpression interfaceName) {
if (currentInfo.addImplementedInterface(interfaceName)) {
populated = true;
return true;
} else {
return false;
}
}
/**
* Records an extended interface type.
*/
public boolean recordExtendedInterface(JSTypeExpression interfaceType) {
if (currentInfo.addExtendedInterface(interfaceType)) {
populated = true;
return true;
} else {
return false;
}
}
/**
* Records that we're lending to another name.
*/
public boolean recordLends(String name) {
if (!hasAnyTypeRelatedTags()) {
currentInfo.setLendsName(name);
populated = true;
return true;
} else {
return false;
}
}
/**
* Returns whether current JSDoc is annotated with {@code @ngInject}.
*/
public boolean isNgInjectRecorded() {
return currentInfo.isNgInject();
}
/**
* Records that we'd like to add {@code $inject} property inferred from
* parameters.
*/
public boolean recordNgInject(boolean ngInject)
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> {
if (!isNgInjectRecorded()) {
currentInfo.setNgInject(ngInject);
populated = true;
return true;
} else {
return false;
}
}
/**
* Returns whether current JSDoc is annotated with {@code @wizaction}.
*/
public boolean isWizactionRecorded() {
return currentInfo.isWizaction();
}
/**
* Records that this method is to be exposed as a wizaction.
*/
public boolean recordWizaction() {
if (!isWizactionRecorded()) {
currentInfo.setWizaction(true);
populated = true;
return true;
} else {
return false;
}
}
/**
* Returns whether current JSDoc is annotated with {@code @disposes}.
*/
public boolean isDisposesRecorded() {
return currentInfo.isDisposes();
}
/**
* Records a parameter that gets disposed.
*
* @return {@code true} if all the parameters was recorded and
* {@code false} if a parameter with the same name was already defined
*/
public boolean recordDisposesParameter(List<String> parameterNames) {
for (String parameterName : parameterNames) {
if ((currentInfo.hasParameter(parameterName) ||
parameterName.equals("*")) &&
currentInfo.setDisposedParameter(parameterName)) {
populated = true;
} else {
return false;
}
}
return true;
}
/**
* Whether the current doc info has other type tags, like
* {@code @param} or {@code @return} or {@code @type} or etc.
*/
private boolean hasAnyTypeRelatedTags() {
return currentInfo.isConstructor() ||
currentInfo.isInterface() ||
currentInfo.getParameterCount() > 0 ||
currentInfo.hasReturnType() ||
currentInfo.hasBaseType() ||
currentInfo.getExtendedInterfacesCount() > 0 ||
currentInfo.getLendsName() != null ||
currentInfo.hasThisType() ||
hasAnySingletonTypeTags();
}
/**
* Whether the current doc info has any of the singleton type
* tags that may not appear with other type tags, like
* {@code @type} or {@code @typedef}.
*/
private boolean hasAnySingletonTypeTags() {
return currentInfo.hasType() ||
currentInfo.hasTypedefType() ||
currentInfo.hasEnumParameterType();
}
/**
* Whether the current doc info has any of the singleton type
* tags that may not appear with other type tags, like
* {@code @type} or {@code @typedef}.
*/
private boolean hasAnySingletonSideEffectTags() {
return currentInfo.isNoSideEffects() ||
currentInfo.hasModifies();
}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> @return a comparator or null (in particular, if not overridden)
*/
public Comparator<DiGraphNode<N, Branch>> getOptionalNodeComparator(
boolean isForward) {
return null;
}
/**
* The edge object for the control flow graph.
*/
public static enum Branch {
/** Edge is taken if the condition is true. */
ON_TRUE,
/** Edge is taken if the condition is false. */
ON_FALSE,
/** Unconditional branch. */
UNCOND,
/**
* Exception-handling code paths.
* Conflates two kind of control flow passing:
* - An exception is thrown, and falls into a catch or finally block
* - During exception handling, a finally block finishes and control
* passes to the next finally block.
* In theory, we need 2 different edge types. In practice, we
* can just treat them as "the edges we can't really optimize".
*/
ON_EX,
/** Possible folded-away template */
SYN_BLOCK;
public boolean isConditional() {
return this == ON_TRUE || this == ON_FALSE;
}
}
/**
* Abstract callback to visit a control flow graph node without going into
* subtrees of the node that are also represented by other
* control flow graph nodes.
*
* <p>For example, traversing an IF node as root will visit the two subtrees
* pointed by the {@link ControlFlowGraph.Branch#ON_TRUE} and
* {@link ControlFlowGraph.Branch#ON_FALSE} edges.
*/
public abstract static class AbstractCfgNodeTraversalCallback implements
Callback {
@Override
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
if (parent == null) {
return true;
}
return !isEnteringNewCfgNode(n);
}
}
/**
* @return True if n should be represented by a new CFG node in the control
* flow graph.
*/
public static boolean isEnteringNewCfgNode(Node n) {
Node parent = n.getParent();
switch (parent.getType()) {
case Token.BLOCK:
case Token.SCRIPT:
case Token.TRY:
return true;
case Token.FUNCTION:
// A function node represents the start of a function where the name
// bleeds into the local scope and parameters are assigned
// to the formal argument names. The node includes the name of the
// function and the LP list since we assume the whole set up process
// is atomic without change in control flow. The next change of
// control is going into the function's body, represented by the second
// child.
return n != parent.getFirstChild().getNext();
case Token.WHILE:
case Token.DO:
case Token.IF:
// These control structures are represented by a node that holds the
// condition. Each of them is a branch node based on its condition.
return NodeUtil.getConditionExpression(parent) != n;
case Token.FOR:
// The FOR(;;) node differs from other control structures in that
// it has an initialization and an increment statement. Those
// two statements have corresponding CFG nodes to represent them.
// The FOR node only represents the condition check for each iteration.
//
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> or returns null if this is not
* a function.
*/
public TemplateType toMaybeTemplateType() {
return null;
}
/**
* Null-safe version of toMaybeTemplateType().
*/
public static TemplateType toMaybeTemplateType(JSType type) {
return type == null ? null : type.toMaybeTemplateType();
}
public boolean hasAnyTemplateTypes() {
if (!this.inTemplatedCheckVisit) {
this.inTemplatedCheckVisit = true;
boolean result = hasAnyTemplateTypesInternal();
this.inTemplatedCheckVisit = false;
return result;
} else {
// prevent infinite recursion, this is "not yet".
return false;
}
}
boolean hasAnyTemplateTypesInternal() {
return templateTypeMap.hasAnyTemplateTypesInternal();
}
/**
* Returns the template type map associated with this type.
*/
public TemplateTypeMap getTemplateTypeMap() {
return templateTypeMap;
}
/**
* Extends the template type map associated with this type, merging in the
* keys and values of the specified map.
*/
public void extendTemplateTypeMap(TemplateTypeMap otherMap) {
templateTypeMap = templateTypeMap.extend(otherMap);
}
/**
* Tests whether this type is an {@code Object}, or any subtype thereof.
* @return {@code this <: Object}
*/
public boolean isObject() {
return false;
}
/**
* Whether this type is a {@link FunctionType} that is a constructor or a
* named type that points to such a type.
*/
public boolean isConstructor() {
return false;
}
/**
* Whether this type is a nominal type (a named instance object or
* a named enum).
*/
public boolean isNominalType() {
return false;
}
/**
* Whether this type is the original constructor of a nominal type.
* Does not include structural constructors.
*/
public final boolean isNominalConstructor() {
if (isConstructor() || isInterface()) {
FunctionType fn = toMaybeFunctionType();
if (fn == null) {
return false;
}
// Programmer-defined constructors will have a link
// back to the original function in the source tree.
// Structural constructors will not.
if (fn.getSource() != null) {
return true;
}
// Native constructors are always nominal.
return fn.isNativeObjectType();
}
return false;
}
/**
* Whether this type is an Instance object of some constructor.
* Does not necessarily mean this is an {@link InstanceObjectType}.
*/
public boolean isInstanceType() {
return false;
}
/**
* Whether this type is a {@link FunctionType} that is an interface or a named
* type that points to such a type.
*/
public boolean isInterface() {
return false;
}
/**
* Whether this type is a {@link FunctionType} that is an ordinary function or
* a named type that points to such a type.
*/
public boolean isOrdinaryFunction() {
return false;
}
/**
* Checks if two types are equivalent.
*/
public final boolean isEquivalentTo(JSType that) {
return checkEquivalenceHelper(that, EquivalenceMethod.IDENTITY);
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>
TemplateTypeMap thatTypeParams = thatType.getTemplateTypeMap();
boolean templateMatch = true;
if (isExemptFromTemplateTypeInvariance(thatType)) {
// Array and Object are exempt from template type invariance; their
// template types maps are considered a match only if the ObjectElementKey
// values are subtypes/supertypes of one another.
TemplateType key = thisType.registry.getObjectElementKey();
JSType thisElement = thisTypeParams.getTemplateType(key);
JSType thatElement = thatTypeParams.getTemplateType(key);
templateMatch = thisElement.isSubtype(thatElement)
|| thatElement.isSubtype(thisElement);
} else {
templateMatch = thisTypeParams.checkEquivalenceHelper(
thatTypeParams, EquivalenceMethod.INVARIANT);
}
if (!templateMatch) {
return false;
}
// Templatized types. The above check guarantees TemplateTypeMap
// equivalence; check if the base type is a subtype.
if (thisType.isTemplatizedType()) {
return thisType.toMaybeTemplatizedType().getReferencedType().isSubtype(
thatType);
}
// proxy types
if (thatType instanceof ProxyObjectType) {
return thisType.isSubtype(
((ProxyObjectType) thatType).getReferencedTypeInternal());
}
return false;
}
/**
* Determines if the specified type is exempt from standard invariant
* templatized typing rules.
*/
static boolean isExemptFromTemplateTypeInvariance(JSType type) {
ObjectType objType = type.toObjectType();
return objType == null ||
"Array".equals(objType.getReferenceName()) ||
"Object".equals(objType.getReferenceName());
}
/**
* Visit this type with the given visitor.
* @see com.google.javascript.rhino.jstype.Visitor
* @return the value returned by the visitor
*/
public abstract <T> T visit(Visitor<T> visitor);
/**
* Visit the types with the given visitor.
* @see com.google.javascript.rhino.jstype.RelationshipVisitor
* @return the value returned by the visitor
*/
abstract <T> T visit(RelationshipVisitor<T> visitor, JSType that);
/**
* Force this type to resolve, even if the registry is in a lazy
* resolving mode.
* @see #resolve
*/
public final JSType forceResolve(ErrorReporter t, StaticScope<JSType> scope) {
ResolveMode oldResolveMode = registry.getResolveMode();
registry.setResolveMode(ResolveMode.IMMEDIATE);
JSType result = resolve(t, scope);
registry.setResolveMode(oldResolveMode);
return result;
}
/**
* Resolve this type in the given scope.
*
* The returned value must be equal to {@code this}, as defined by
* {@link #isEquivalentTo}. It may or may not be the same object. This method
* may modify the internal state of {@code this}, as long as it does
* so in a way that preserves Object equality.
*
* For efficiency, we should only resolve a type once per compilation job.
* For incremental compilations, one compilation job may need
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> the
* artifacts from a previous generation, so we will eventually need
* a generational flag instead of a boolean one.
*/
public final JSType resolve(ErrorReporter t, StaticScope<JSType> scope) {
if (resolved) {
// TODO(nicksantos): Check to see if resolve() looped back on itself.
// Preconditions.checkNotNull(resolveResult);
if (resolveResult == null) {
return registry.getNativeType(JSTypeNative.UNKNOWN_TYPE);
}
return resolveResult;
}
resolved = true;
resolveResult = resolveInternal(t, scope);
resolveResult.setResolvedTypeInternal(resolveResult);
return resolveResult;
}
/**
* @see #resolve
*/
abstract JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope);
void setResolvedTypeInternal(JSType type) {
resolveResult = type;
resolved = true;
}
/** Whether the type has been resolved. */
public final boolean isResolved() {
return resolved;
}
/** Clears the resolved field. */
public final void clearResolved() {
resolved = false;
resolveResult = null;
}
/**
* A null-safe resolve.
* @see #resolve
*/
static final JSType safeResolve(
JSType type, ErrorReporter t, StaticScope<JSType> scope) {
return type == null ? null : type.resolve(t, scope);
}
/**
* Certain types have constraints on them at resolution-time.
* For example, a type in an {@code @extends} annotation must be an
* object. Clients should inject a validator that emits a warning
* if the type does not validate, and return false.
*/
public boolean setValidator(Predicate<JSType> validator) {
return validator.apply(this);
}
public static class TypePair {
public final JSType typeA;
public final JSType typeB;
public TypePair(JSType typeA, JSType typeB) {
this.typeA = typeA;
this.typeB = typeB;
}
}
/**
* A string representation of this type, suitable for printing
* in warnings.
*/
@Override
public String toString() {
return toStringHelper(false);
}
/**
* A hash code function for diagnosing complicated issues
* around type-identity.
*/
public String toDebugHashCodeString() {
return "{" + hashCode() + "}";
}
/**
* A string representation of this type, suitable for printing
* in type annotations at code generation time.
*/
public final String toAnnotationString() {
return toStringHelper(true);
}
/**
* @param forAnnotations Whether this is for use in code generator
* annotations. Otherwise, it's for warnings.
*/
abstract String toStringHelper(boolean forAnnotations);
/**
* Modify this type so that it matches the specified type.
*
* This is useful for reverse type-inference, where we want to
* infer that an object literal matches its constraint (much like
* how the java compiler does reverse-inference to figure out generics).
* @param constraint
*/
public void matchConstraint(JSType constraint) {}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> getPossibleToBooleanOutcomes() {
return BooleanLiteralSet.TRUE;
}
@Override
JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) {
returnType = safeResolve(returnType, t, scope);
if (parameters != null) {
for (Node paramNode = parameters.getFirstChild();
paramNode != null; paramNode = paramNode.getNext()) {
paramNode.setJSType(paramNode.getJSType().resolve(t, scope));
}
}
return this;
}
boolean hasUnknownParamsOrReturn() {
if (parameters != null) {
for (Node paramNode = parameters.getFirstChild();
paramNode != null; paramNode = paramNode.getNext()) {
JSType type = paramNode.getJSType();
if (type == null || type.isUnknownType()) {
return true;
}
}
}
return returnType == null || returnType.isUnknownType();
}
@Override
String toStringHelper(boolean forAnnotations) {
return "[ArrowType]";
}
@Override
public boolean hasAnyTemplateTypesInternal() {
return returnType.hasAnyTemplateTypes()
|| hasTemplatedParameterType();
}
private boolean hasTemplatedParameterType() {
if (parameters != null) {
for (Node paramNode = parameters.getFirstChild();
paramNode != null; paramNode = paramNode.getNext()) {
JSType type = paramNode.getJSType();
if (type != null && type.hasAnyTemplateTypes()) {
return true;
}
}
}
return false;
}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> description found on that annotation and, if applicable,
* the type declaration. All this information is only collected
* if documentation collection is turned on.
*/
public static final class Marker {
private TrimmedStringPosition annotation = null;
private TrimmedStringPosition name = null;
private SourcePosition<Node> nameNode = null;
private StringPosition description = null;
private TypePosition type = null;
/**
* Gets the position information for the annotation name. (e.g., "param")
*/
public StringPosition getAnnotation() {
return annotation;
}
void setAnnotation(TrimmedStringPosition p) {
annotation = p;
}
/**
* Gets the position information for the name found
* in a @param tag.
* @deprecated Use #getNameNode
*/
@Deprecated
public StringPosition getName() {
return name;
}
void setName(TrimmedStringPosition p) {
name = p;
}
/**
* Gets the position information for the name found
* in an @param tag.
*/
public SourcePosition<Node> getNameNode() {
return nameNode;
}
void setNameNode(SourcePosition<Node> p) {
nameNode = p;
}
/**
* Gets the position information for the description found
* in a block tag.
*/
public StringPosition getDescription() {
return description;
}
void setDescription(StringPosition p) {
description = p;
}
/**
* Gets the position information for the type expression found
* in some block tags, like "@param" and "@return".
*/
public TypePosition getType() {
return type;
}
void setType(TypePosition p) {
type = p;
}
}
private LazilyInitializedInfo info = null;
private LazilyInitializedDocumentation documentation = null;
// The Node this JSDoc is associated with.
private Node associatedNode = null;
private Visibility visibility = null;
/**
* The {@link #isConstant()}, {@link #isConstructor()}, {@link #isInterface},
* {@link #isHidden()} and {@link #shouldPreserveTry()} flags as well as
* whether the {@link #type} field stores a value for {@link #getType()},
* {@link #getReturnType()} or {@link #getEnumParameterType()}.
*
* @see #setFlag(boolean, int)
* @see #getFlag(int)
* @see #setType(JSTypeExpression, int)
* @see #getType(int)
*/
private int bitset = 0x00;
/**
* The type for {@link #getType()}, {@link #getReturnType()} or
* {@link #getEnumParameterType()}. The knowledge of which one is recorded is
* stored in the {@link #bitset} field.
*
* @see #setType(JSTypeExpression, int)
* @see #getType(int)
*/
private JSTypeExpression type = null;
/**
* The type for {@link #getThisType()}.
*/
private JSTypeExpression thisType = null;
/**
* Whether to include documentation.
*
* @see JSDocInfo.LazilyInitializedDocumentation
*/
private boolean includeDocumentation = false;
/**
* Position of the original comment
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>) {
setFlag(value, MASK_IMPLICITCAST);
}
void setNoSideEffects(boolean value) {
setFlag(value, MASK_NOSIDEEFFECTS);
}
void setExterns(boolean value) {
setFlag(value, MASK_EXTERNS);
}
void setJavaDispatch(boolean value) {
setFlag(value, MASK_JAVADISPATCH);
}
void setNoCompile(boolean value) {
setFlag(value, MASK_NOCOMPILE);
}
private void setFlag(boolean value, int mask) {
if (value) {
bitset |= mask;
} else {
bitset &= ~mask;
}
}
/**
* @return whether the {@code @consistentIdGenerator} is present on
* this {@link JSDocInfo}
*/
public boolean isConsistentIdGenerator() {
return getFlag(MASK_CONSISTIDGEN);
}
/**
* @return whether the {@code @stableIdGenerator} is present on this {@link JSDocInfo}.
*/
public boolean isStableIdGenerator() {
return getFlag(MASK_STALBEIDGEN);
}
/**
* @return whether the {@code @stableIdGenerator} is present on this {@link JSDocInfo}.
*/
public boolean isMappedIdGenerator() {
return getFlag(MASK_MAPPEDIDGEN);
}
/**
* Returns whether the {@code @const} annotation is present on this
* {@link JSDocInfo}.
*/
public boolean isConstant() {
return getFlag(MASK_CONSTANT) || isDefine();
}
/**
* Returns whether the {@code @constructor} annotation is present on this
* {@link JSDocInfo}.
*/
public boolean isConstructor() {
return getFlag(MASK_CONSTRUCTOR);
}
/**
* Returns whether the {@code @struct} annotation is present on this
* {@link JSDocInfo}.
*/
public boolean makesStructs() {
return getFlag(MASK_STRUCT);
}
/**
* Returns whether the {@code @dict} annotation is present on this
* {@link JSDocInfo}.
*/
public boolean makesDicts() {
return getFlag(MASK_DICT);
}
/**
* Returns whether the {@code @define} annotation is present on this
* {@link JSDocInfo}. If this annotation is present, then the
* {@link #getType()} method will retrieve the define type.
*/
public boolean isDefine() {
return getFlag(MASK_DEFINE);
}
/**
* Returns whether the {@code @hidden} annotation is present on this
* {@link JSDocInfo}.
*/
public boolean isHidden() {
return getFlag(MASK_HIDDEN);
}
/**
* Returns whether the {@code @nocheck} annotation is present on this
* {@link JSDocInfo}.
*/
public boolean isNoTypeCheck() {
return getFlag(MASK_NOCHECK);
}
/**
* Returns whether the {@code @preserveTry} annotation is present on this
* {@link JSDocInfo}.
*/
public boolean shouldPreserveTry() {
return getFlag(MASK_PRESERVETRY);
}
/**
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>
* Returns whether the {@code @override} annotation is present on this
* {@link JSDocInfo}.
*/
public boolean isOverride() {
return getFlag(MASK_OVERRIDE);
}
/**
* Returns whether the {@code @noalias} annotation is present on this
* {@link JSDocInfo}.
*/
public boolean isNoAlias() {
return getFlag(MASK_NOALIAS);
}
/**
* Returns whether the {@code @deprecated} annotation is present on this
* {@link JSDocInfo}.
*/
public boolean isDeprecated() {
return getFlag(MASK_DEPRECATED);
}
/**
* Returns whether the {@code @interface} annotation is present on this
* {@link JSDocInfo}.
*/
public boolean isInterface() {
return getFlag(MASK_INTERFACE);
}
/**
* Returns whether the {@code @export} annotation is present on this
* {@link JSDocInfo}.
*/
public boolean isExport() {
return getFlag(MASK_EXPORT);
}
/**
* Returns whether the {@code @expose} annotation is present on this
* {@link JSDocInfo}.
*/
public boolean isExpose() {
return getFlag(MASK_EXPOSE);
}
/**
* Returns whether the {@code @noshadow} annotation is present on this
* {@link JSDocInfo}.
*/
public boolean isNoShadow() {
return getFlag(MASK_NOSHADOW);
}
/**
* @return whether the {@code @idGenerator} is present on
* this {@link JSDocInfo}
*/
public boolean isIdGenerator() {
return getFlag(MASK_IDGEN);
}
/**
* Returns whether the {@code @implicitCast} annotation is present on this
* {@link JSDocInfo}.
*/
public boolean isImplicitCast() {
return getFlag(MASK_IMPLICITCAST);
}
/**
* Returns whether the {@code @nosideeffects} annotation is present on this
* {@link JSDocInfo}.
*/
public boolean isNoSideEffects() {
return getFlag(MASK_NOSIDEEFFECTS);
}
/**
* Returns whether the {@code @externs} annotation is present on this
* {@link JSDocInfo}.
*/
public boolean isExterns() {
return getFlag(MASK_EXTERNS);
}
/**
* Returns whether the {@code @javadispatch} annotation is present on this
* {@link JSDocInfo}.
*/
public boolean isJavaDispatch() {
return getFlag(MASK_JAVADISPATCH);
}
/**
* Returns whether the {@code @nocompile} annotation is present on this
* {@link JSDocInfo}.
*/
public boolean isNoCompile() {
return getFlag(MASK_NOCOMPILE);
}
/**
* @return Whether there is declaration present on this {@link JSDocInfo}.
*/
public boolean containsDeclaration() {
return (hasType()
|| hasReturnType()
|| hasEnumParameterType()
|| hasTypedefType()
|| hasThisType()
|| getParameterCount() > 0
|| getFlag(MASK_CONSTANT
| MASK_CONSTRUCTOR
| MASK_DEFINE
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> {
return 0;
}
return info.parameters.size();
}
void setType(JSTypeExpression type) {
setType(type, TYPEFIELD_TYPE);
}
void setReturnType(JSTypeExpression type) {
setType(type, TYPEFIELD_RETURN);
}
void setEnumParameterType(JSTypeExpression type) {
setType(type, TYPEFIELD_ENUM);
}
void setTypedefType(JSTypeExpression type) {
setType(type, TYPEFIELD_TYPEDEF);
}
private void setType(JSTypeExpression type, int mask) {
if ((bitset & MASK_TYPEFIELD) != 0) {
throw new IllegalStateException(
"API tried to add two incompatible type tags. " +
"This should have been blocked and emitted a warning.");
}
this.bitset = (bitset & MASK_FLAGS) | mask;
this.type = type;
}
/**
* Returns the list of thrown types.
*/
public List<JSTypeExpression> getThrownTypes() {
if (info == null || info.thrownTypes == null) {
return ImmutableList.of();
}
return Collections.unmodifiableList(info.thrownTypes);
}
/**
* Returns whether a type, specified using the {@code @type} annotation, is
* present on this JSDoc.
*/
public boolean hasType() {
return hasType(TYPEFIELD_TYPE);
}
/**
* Returns whether an enum parameter type, specified using the {@code @enum}
* annotation, is present on this JSDoc.
*/
public boolean hasEnumParameterType() {
return hasType(TYPEFIELD_ENUM);
}
/**
* Returns whether a typedef parameter type, specified using the
* {@code @typedef} annotation, is present on this JSDoc.
*/
public boolean hasTypedefType() {
return hasType(TYPEFIELD_TYPEDEF);
}
/**
* Returns whether this {@link JSDocInfo} contains a type for {@code @return}
* annotation.
*/
public boolean hasReturnType() {
return hasType(TYPEFIELD_RETURN);
}
private boolean hasType(int mask) {
return (bitset & MASK_TYPEFIELD) == mask;
}
/**
* Gets the type specified by the {@code @type} annotation.
*/
public JSTypeExpression getType() {
return getType(TYPEFIELD_TYPE);
}
/**
* Gets the return type specified by the {@code @return} annotation.
*/
public JSTypeExpression getReturnType() {
return getType(TYPEFIELD_RETURN);
}
/**
* Gets the enum parameter type specified by the {@code @enum} annotation.
*/
public JSTypeExpression getEnumParameterType() {
return getType(TYPEFIELD_ENUM);
}
/**
* Gets the typedef type specified by the {@code @type} annotation.
*/
public JSTypeExpression getTypedefType() {
return getType(TYPEFIELD_TYPEDEF);
}
private JSTypeExpression getType(int typefield) {
if ((MASK_TYPEFIELD & bitset) == typefield) {
return type;
} else {
return null;
}
}
/**
* Gets the type specified by the {@code @this} annotation
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>
return propertyNode == null ? null : propertyNode.getStaticSourceFile();
}
@Override
public Property getSymbol() {
return this;
}
@Override
public Property getDeclaration() {
return propertyNode == null ? null : this;
}
@Override
public JSType getType() {
return type;
}
@Override
public boolean isTypeInferred() {
return inferred;
}
boolean isFromExterns() {
return propertyNode == null ? false : propertyNode.isFromExterns();
}
void setType(JSType type) {
this.type = type;
}
@Override public JSDocInfo getJSDocInfo() {
return this.docInfo;
}
void setJSDocInfo(JSDocInfo info) {
this.docInfo = info;
}
public void setNode(Node n) {
this.propertyNode = n;
}
}
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> PropAccess { ANY, STRUCT, DICT }
/**
* {@code [[Call]]} property.
*/
private ArrowType call;
/**
* The {@code prototype} property. This field is lazily initialized by
* {@code #getPrototype()}. The most important reason for lazily
* initializing this field is that there are cycles in the native types
* graph, so some prototypes must temporarily be {@code null} during
* the construction of the graph.
*
* If non-null, the type must be a PrototypeObjectType.
*/
private Property prototypeSlot;
/**
* Whether a function is a constructor, an interface, or just an ordinary
* function.
*/
private final Kind kind;
/**
* Whether the instances are structs, dicts, or unrestricted.
*/
private PropAccess propAccess;
/**
* The type of {@code this} in the scope of this function.
*/
private JSType typeOfThis;
/**
* The function node which this type represents. It may be {@code null}.
*/
private Node source;
/**
* The interfaces directly implemented by this function (for constructors)
* It is only relevant for constructors. May not be {@code null}.
*/
private List<ObjectType> implementedInterfaces = ImmutableList.of();
/**
* The interfaces directly extended by this function (for interfaces)
* It is only relevant for constructors. May not be {@code null}.
*/
private List<ObjectType> extendedInterfaces = ImmutableList.of();
/**
* The types which are subtypes of this function. It is only relevant for
* constructors and may be {@code null}.
*/
private List<FunctionType> subTypes;
/** Creates an instance for a function that might be a constructor. */
FunctionType(JSTypeRegistry registry, String name, Node source,
ArrowType arrowType, JSType typeOfThis,
TemplateTypeMap templateTypeMap,
boolean isConstructor, boolean nativeType) {
super(registry, name,
registry.getNativeObjectType(JSTypeNative.FUNCTION_INSTANCE_TYPE),
nativeType, templateTypeMap);
setPrettyPrint(true);
Preconditions.checkArgument(source == null ||
Token.FUNCTION == source.getType());
Preconditions.checkNotNull(arrowType);
this.source = source;
if (isConstructor) {
this.kind = Kind.CONSTRUCTOR;
this.propAccess = PropAccess.ANY;
this.typeOfThis = typeOfThis != null ?
typeOfThis : new InstanceObjectType(registry, this, nativeType);
} else {
this.kind = Kind.ORDINARY;
this.typeOfThis = typeOfThis != null ?
typeOfThis :
registry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE);
}
this.call = arrowType;
}
/** Creates an instance for a function that is an interface. */
private FunctionType(JSTypeRegistry registry, String name, Node source,
TemplateTypeMap typeParameters) {
super(registry, name,
registry.getNativeObjectType(JSTypeNative.FUNCTION_INSTANCE_TYPE),
false, typeParameters);
setPrettyPrint(true);
Preconditions.checkArgument(source == null ||
Token.FUNCTION == source.getType());
Preconditions.checkArgument(name != null);
this.source = source;
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>
this.call = new ArrowType(registry, new Node(Token.PARAM_LIST), null);
this.kind = Kind.INTERFACE;
this.typeOfThis = new InstanceObjectType(registry, this);
}
/** Creates an instance for a function that is an interface. */
static FunctionType forInterface(
JSTypeRegistry registry, String name, Node source,
TemplateTypeMap typeParameters) {
return new FunctionType(registry, name, source, typeParameters);
}
@Override
public boolean isInstanceType() {
// The universal constructor is its own instance, bizarrely. It overrides
// getConstructor() appropriately when it's declared.
return this == registry.getNativeType(U2U_CONSTRUCTOR_TYPE);
}
@Override
public boolean isConstructor() {
return kind == Kind.CONSTRUCTOR;
}
@Override
public boolean isInterface() {
return kind == Kind.INTERFACE;
}
@Override
public boolean isOrdinaryFunction() {
return kind == Kind.ORDINARY;
}
/**
* When a class B inherits from A and A is annotated as a struct, then B
* automatically gets the annotation, even if B's constructor is not
* explicitly annotated.
*/
public boolean makesStructs() {
if (!isConstructor()) {
return false;
}
if (propAccess == PropAccess.STRUCT) {
return true;
}
FunctionType superc = getSuperClassConstructor();
if (superc != null && superc.makesStructs()) {
setStruct();
return true;
}
return false;
}
/**
* When a class B inherits from A and A is annotated as a dict, then B
* automatically gets the annotation, even if B's constructor is not
* explicitly annotated.
*/
public boolean makesDicts() {
if (!isConstructor()) {
return false;
}
if (propAccess == PropAccess.DICT) {
return true;
}
FunctionType superc = getSuperClassConstructor();
if (superc != null && superc.makesDicts()) {
setDict();
return true;
}
return false;
}
public void setStruct() {
propAccess = PropAccess.STRUCT;
}
public void setDict() {
propAccess = PropAccess.DICT;
}
@Override
public FunctionType toMaybeFunctionType() {
return this;
}
@Override
public boolean canBeCalled() {
return true;
}
public boolean hasImplementedInterfaces() {
if (!implementedInterfaces.isEmpty()){
return true;
}
FunctionType superCtor = isConstructor() ?
getSuperClassConstructor() : null;
if (superCtor != null) {
return superCtor.hasImplementedInterfaces();
}
return false;
}
public Iterable<Node> getParameters() {
Node n = getParametersNode();
if (n != null) {
return n.children();
} else {
return Collections.emptySet();
}
}
/** Gets an LP node that contains all params. May be null. */
public Node getParametersNode() {
return call.parameters;
}
/** Gets the minimum number of arguments that this function requires. */
public int getMinArguments() {
// NOTE(
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>nicksantos): There are some native functions that have optional
// parameters before required parameters. This algorithm finds the position
// of the last required parameter.
int i = 0;
int min = 0;
for (Node n : getParameters()) {
i++;
if (!n.isOptionalArg() && !n.isVarArgs()) {
min = i;
}
}
return min;
}
/**
* Gets the maximum number of arguments that this function requires,
* or Integer.MAX_VALUE if this is a variable argument function.
*/
public int getMaxArguments() {
Node params = getParametersNode();
if (params != null) {
Node lastParam = params.getLastChild();
if (lastParam == null || !lastParam.isVarArgs()) {
return params.getChildCount();
}
}
return Integer.MAX_VALUE;
}
public JSType getReturnType() {
return call.returnType;
}
public boolean isReturnTypeInferred() {
return call.returnTypeInferred;
}
/** Gets the internal arrow type. For use by subclasses only. */
ArrowType getInternalArrowType() {
return call;
}
@Override
public Property getSlot(String name) {
if ("prototype".equals(name)) {
// Lazy initialization of the prototype field.
getPrototype();
return prototypeSlot;
} else {
return super.getSlot(name);
}
}
/**
* Includes the prototype iff someone has created it. We do not want
* to expose the prototype for ordinary functions.
*/
@Override
public Set<String> getOwnPropertyNames() {
if (prototypeSlot == null) {
return super.getOwnPropertyNames();
} else {
Set<String> names = Sets.newHashSet("prototype");
names.addAll(super.getOwnPropertyNames());
return names;
}
}
/**
* Gets the {@code prototype} property of this function type. This is
* equivalent to {@code (ObjectType) getPropertyType("prototype")}.
*/
public ObjectType getPrototype() {
// lazy initialization of the prototype field
if (prototypeSlot == null) {
String refName = getReferenceName();
if (refName == null) {
// Someone is trying to access the prototype of a structural function.
// We don't want to give real properties to this prototype, because
// then it would propagate to all structural functions.
setPrototypeNoCheck(
registry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE),
null);
} else {
setPrototype(
new PrototypeObjectType(
registry,
getReferenceName() + ".prototype",
registry.getNativeObjectType(OBJECT_TYPE),
isNativeObjectType(), null),
null);
}
}
return (ObjectType) prototypeSlot.getType();
}
/**
* Sets the prototype, creating the prototype object from the given
* base type.
* @param baseType The base type.
*/
public void setPrototypeBasedOn(ObjectType baseType) {
setPrototypeBasedOn(baseType, null);
}
void setPrototypeBasedOn(ObjectType baseType, Node propertyNode) {
// This is a bit weird. We need to successfully handle these
// two cases:
// Foo.prototype = new Bar();
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> // and
// Foo.prototype = {baz: 3};
// In the first case, we do not want new properties to get
// added to Bar. In the second case, we do want new properties
// to get added to the type of the anonymous object.
//
// We handle this by breaking it into two cases:
//
// In the first case, we create a new PrototypeObjectType and set
// its implicit prototype to the type being assigned. This ensures
// that Bar will not get any properties of Foo.prototype, but properties
// later assigned to Bar will get inherited properly.
//
// In the second case, we just use the anonymous object as the prototype.
if (baseType.hasReferenceName() ||
isNativeObjectType() ||
baseType.isFunctionPrototypeType()) {
baseType = new PrototypeObjectType(
registry, getReferenceName() + ".prototype", baseType);
}
setPrototype(baseType, propertyNode);
}
/**
* Extends the TemplateTypeMap of the function's this type, based on the
* specified type.
* @param type
*/
public void extendTemplateTypeMapBasedOn(ObjectType type) {
typeOfThis.extendTemplateTypeMap(type.getTemplateTypeMap());
}
/**
* Sets the prototype.
* @param prototype the prototype. If this value is {@code null} it will
* silently be discarded.
*/
boolean setPrototype(ObjectType prototype, Node propertyNode) {
if (prototype == null) {
return false;
}
// getInstanceType fails if the function is not a constructor
if (isConstructor() && prototype == getInstanceType()) {
return false;
}
return setPrototypeNoCheck(prototype, propertyNode);
}
/** Set the prototype without doing any sanity checks. */
private boolean setPrototypeNoCheck(ObjectType prototype, Node propertyNode) {
ObjectType oldPrototype = prototypeSlot == null
? null : (ObjectType) prototypeSlot.getType();
boolean replacedPrototype = oldPrototype != null;
this.prototypeSlot = new Property("prototype", prototype, true,
propertyNode == null ? source : propertyNode);
prototype.setOwnerFunction(this);
if (oldPrototype != null) {
// Disassociating the old prototype makes this easier to debug--
// we don't have to worry about two prototypes running around.
oldPrototype.setOwnerFunction(null);
}
if (isConstructor() || isInterface()) {
FunctionType superClass = getSuperClassConstructor();
if (superClass != null) {
superClass.addSubType(this);
}
if (isInterface()) {
for (ObjectType interfaceType : getExtendedInterfaces()) {
if (interfaceType.getConstructor() != null) {
interfaceType.getConstructor().addSubType(this);
}
}
}
}
if (replacedPrototype) {
clearCachedValues();
}
return true;
}
/**
* Returns all interfaces implemented by a class or its superclass and any
* superclasses for any of those interfaces. If this is called before all
* types are resolved, it may return an incomplete set.
*/
public Iterable<ObjectType> getAllImplementedInterfaces() {
// Store them in a linked hash set, so that the compile job is
// deterministic.
Set<ObjectType> interfaces = Sets.
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>newLinkedHashSet();
for (ObjectType type : getImplementedInterfaces()) {
addRelatedInterfaces(type, interfaces);
}
return interfaces;
}
private void addRelatedInterfaces(ObjectType instance, Set<ObjectType> set) {
FunctionType constructor = instance.getConstructor();
if (constructor != null) {
if (!constructor.isInterface()) {
return;
}
set.add(instance);
for (ObjectType interfaceType : instance.getCtorExtendedInterfaces()) {
addRelatedInterfaces(interfaceType, set);
}
}
}
/** Returns interfaces implemented directly by a class or its superclass. */
public Iterable<ObjectType> getImplementedInterfaces() {
FunctionType superCtor = isConstructor() ?
getSuperClassConstructor() : null;
if (superCtor == null) {
return implementedInterfaces;
} else {
return Iterables.concat(
implementedInterfaces, superCtor.getImplementedInterfaces());
}
}
/** Returns interfaces directly implemented by the class. */
public Iterable<ObjectType> getOwnImplementedInterfaces() {
return implementedInterfaces;
}
public void setImplementedInterfaces(List<ObjectType> implementedInterfaces) {
if (isConstructor()) {
// Records this type for each implemented interface.
for (ObjectType type : implementedInterfaces) {
registry.registerTypeImplementingInterface(this, type);
typeOfThis.extendTemplateTypeMap(type.getTemplateTypeMap());
}
this.implementedInterfaces = ImmutableList.copyOf(implementedInterfaces);
} else {
throw new UnsupportedOperationException();
}
}
/**
* Returns all extended interfaces declared by an interfaces or its super-
* interfaces. If this is called before all types are resolved, it may return
* an incomplete set.
*/
public Iterable<ObjectType> getAllExtendedInterfaces() {
// Store them in a linked hash set, so that the compile job is
// deterministic.
Set<ObjectType> extendedInterfaces = Sets.newLinkedHashSet();
for (ObjectType interfaceType : getExtendedInterfaces()) {
addRelatedExtendedInterfaces(interfaceType, extendedInterfaces);
}
return extendedInterfaces;
}
private void addRelatedExtendedInterfaces(ObjectType instance,
Set<ObjectType> set) {
FunctionType constructor = instance.getConstructor();
if (constructor != null) {
set.add(instance);
for (ObjectType interfaceType : constructor.getExtendedInterfaces()) {
addRelatedExtendedInterfaces(interfaceType, set);
}
}
}
/** Returns interfaces directly extended by an interface */
public Iterable<ObjectType> getExtendedInterfaces() {
return extendedInterfaces;
}
/** Returns the number of interfaces directly extended by an interface */
public int getExtendedInterfacesCount() {
return extendedInterfaces.size();
}
public void setExtendedInterfaces(List<ObjectType> extendedInterfaces)
throws UnsupportedOperationException {
if (isInterface()) {
this.extendedInterfaces = ImmutableList.copyOf(extendedInterfaces);
for (ObjectType extendedInterface : this.extendedInterfaces) {
typeOfThis.extendTemplateTypeMap(
extendedInterface.getTemplateTypeMap());
}
} else {
throw new UnsupportedOperationException();
}
}
@Override
public JSType getPropertyType(String name) {
if (!hasOwnProperty(name)) {
// Define the "call", "apply", and "bind" functions lazily.
boolean isCall = "call".equals(name);
boolean isBind =
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>
// are optional. It's sufficient to check the first argument.
Node firstArg = thisTypeNode.getNext();
if (firstArg == null
|| firstArg.isOptionalArg()
|| firstArg.isVarArgs()) {
thisTypeNode.setOptionalArg(true);
}
}
builder.withParamsNode(params);
}
return builder.build();
}
@Override
boolean defineProperty(String name, JSType type,
boolean inferred, Node propertyNode) {
if ("prototype".equals(name)) {
ObjectType objType = type.toObjectType();
if (objType != null) {
if (prototypeSlot != null &&
objType.isEquivalentTo(prototypeSlot.getType())) {
return true;
}
setPrototypeBasedOn(objType, propertyNode);
return true;
} else {
return false;
}
}
return super.defineProperty(name, type, inferred, propertyNode);
}
/**
* Computes the supremum or infimum of two functions.
* Because sup() and inf() share a lot of logic for functions, we use
* a single helper.
* @param leastSuper If true, compute the supremum of {@code this} with
* {@code that}. Otherwise, compute the infimum.
* @return The least supertype or greatest subtype.
*/
FunctionType supAndInfHelper(FunctionType that, boolean leastSuper) {
// NOTE(nicksantos): When we remove the unknown type, the function types
// form a lattice with the universal constructor at the top of the lattice,
// and the LEAST_FUNCTION_TYPE type at the bottom of the lattice.
//
// When we introduce the unknown type, it's much more difficult to make
// heads or tails of the partial ordering of types, because there's no
// clear hierarchy between the different components (parameter types and
// return types) in the ArrowType.
//
// Rather than make the situation more complicated by introducing new
// types (like unions of functions), we just fallback on the simpler
// approach of getting things right at the top and the bottom of the
// lattice.
//
// If there are unknown parameters or return types making things
// ambiguous, then sup(A, B) is always the top function type, and
// inf(A, B) is always the bottom function type.
Preconditions.checkNotNull(that);
if (isEquivalentTo(that)) {
return this;
}
// If these are ordinary functions, then merge them.
// Don't do this if any of the params/return
// values are unknown, because then there will be cycles in
// their local lattice and they will merge in weird ways.
if (isOrdinaryFunction() && that.isOrdinaryFunction() &&
!this.call.hasUnknownParamsOrReturn() &&
!that.call.hasUnknownParamsOrReturn()) {
// Check for the degenerate case, but double check
// that there's not a cycle.
boolean isSubtypeOfThat = isSubtype(that);
boolean isSubtypeOfThis = that.isSubtype(this);
if (isSubtypeOfThat && !isSubtypeOfThis) {
return leastSuper ? that : this;
} else if (isSubtypeOfThis && !
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>isSubtypeOfThat) {
return leastSuper ? this : that;
}
// Merge the two functions component-wise.
FunctionType merged = tryMergeFunctionPiecewise(that, leastSuper);
if (merged != null) {
return merged;
}
}
// The function instance type is a special case
// that lives above the rest of the lattice.
JSType functionInstance = registry.getNativeType(
JSTypeNative.FUNCTION_INSTANCE_TYPE);
if (functionInstance.isEquivalentTo(that)) {
return leastSuper ? that : this;
} else if (functionInstance.isEquivalentTo(this)) {
return leastSuper ? this : that;
}
// In theory, we should be using the GREATEST_FUNCTION_TYPE as the
// greatest function. In practice, we don't because it's way too
// broad. The greatest function takes var_args None parameters, which
// means that all parameters register a type warning.
//
// Instead, we use the U2U ctor type, which has unknown type args.
FunctionType greatestFn =
registry.getNativeFunctionType(JSTypeNative.U2U_CONSTRUCTOR_TYPE);
FunctionType leastFn =
registry.getNativeFunctionType(JSTypeNative.LEAST_FUNCTION_TYPE);
return leastSuper ? greatestFn : leastFn;
}
/**
* Try to get the sup/inf of two functions by looking at the
* piecewise components.
*/
private FunctionType tryMergeFunctionPiecewise(
FunctionType other, boolean leastSuper) {
Node newParamsNode = null;
if (call.hasEqualParameters(other.call, EquivalenceMethod.IDENTITY)) {
newParamsNode = call.parameters;
} else {
// If the parameters are not equal, don't try to merge them.
// Someday, we should try to merge the individual params.
return null;
}
JSType newReturnType = leastSuper ?
call.returnType.getLeastSupertype(other.call.returnType) :
call.returnType.getGreatestSubtype(other.call.returnType);
JSType newTypeOfThis = null;
if (isEquivalent(typeOfThis, other.typeOfThis)) {
newTypeOfThis = typeOfThis;
} else {
JSType maybeNewTypeOfThis = leastSuper ?
typeOfThis.getLeastSupertype(other.typeOfThis) :
typeOfThis.getGreatestSubtype(other.typeOfThis);
newTypeOfThis = maybeNewTypeOfThis;
}
boolean newReturnTypeInferred =
call.returnTypeInferred || other.call.returnTypeInferred;
return new FunctionType(
registry, null, null,
new ArrowType(
registry, newParamsNode, newReturnType, newReturnTypeInferred),
newTypeOfThis, null, false, false);
}
/**
* Given a constructor or an interface type, get its superclass constructor
* or {@code null} if none exists.
*/
public FunctionType getSuperClassConstructor() {
Preconditions.checkArgument(isConstructor() || isInterface());
ObjectType maybeSuperInstanceType = getPrototype().getImplicitPrototype();
if (maybeSuperInstanceType == null) {
return null;
}
return maybeSuperInstanceType.getConstructor();
}
/**
* Given an
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> interface and a property, finds the top-most super interface
* that has the property defined (including this interface).
*/
public static ObjectType getTopDefiningInterface(ObjectType type,
String propertyName) {
ObjectType foundType = null;
if (type.hasProperty(propertyName)) {
foundType = type;
}
for (ObjectType interfaceType : type.getCtorExtendedInterfaces()) {
if (interfaceType.hasProperty(propertyName)) {
foundType = getTopDefiningInterface(interfaceType, propertyName);
}
}
return foundType;
}
/**
* Given a constructor or an interface type and a property, finds the
* top-most superclass that has the property defined (including this
* constructor).
*/
public ObjectType getTopMostDefiningType(String propertyName) {
Preconditions.checkState(isConstructor() || isInterface());
Preconditions.checkArgument(getInstanceType().hasProperty(propertyName));
FunctionType ctor = this;
if (isInterface()) {
return getTopDefiningInterface(getInstanceType(), propertyName);
}
ObjectType topInstanceType = null;
do {
topInstanceType = ctor.getInstanceType();
ctor = ctor.getSuperClassConstructor();
} while (ctor != null
&& ctor.getPrototype().hasProperty(propertyName));
return topInstanceType;
}
/**
* Two function types are equal if their signatures match. Since they don't
* have signatures, two interfaces are equal if their names match.
*/
boolean checkFunctionEquivalenceHelper(
FunctionType that, EquivalenceMethod eqMethod) {
if (isConstructor()) {
if (that.isConstructor()) {
return this == that;
}
return false;
}
if (isInterface()) {
if (that.isInterface()) {
return getReferenceName().equals(that.getReferenceName());
}
return false;
}
if (that.isInterface()) {
return false;
}
return typeOfThis.checkEquivalenceHelper(that.typeOfThis, eqMethod) &&
call.checkArrowEquivalenceHelper(that.call, eqMethod);
}
@Override
public int hashCode() {
return isInterface() ? getReferenceName().hashCode() : call.hashCode();
}
public boolean hasEqualCallType(FunctionType otherType) {
return this.call.checkArrowEquivalenceHelper(
otherType.call, EquivalenceMethod.IDENTITY);
}
/**
* Informally, a function is represented by
* {@code function (params): returnType} where the {@code params} is a comma
* separated list of types, the first one being a special
* {@code this:T} if the function expects a known type for {@code this}.
*/
@Override
String toStringHelper(boolean forAnnotations) {
if (!isPrettyPrint() ||
this == registry.getNativeType(JSTypeNative.FUNCTION_INSTANCE_TYPE)) {
return "Function";
}
setPrettyPrint(false);
StringBuilder b = new StringBuilder(32);
b.append("function (");
int paramNum = call.parameters.getChildCount();
boolean hasKnownTypeOfThis = !(typeOfThis instanceof UnknownType);
if (hasKnownTypeOfThis) {
if (isConstructor()) {
b.append("new:");
} else {
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS> b.append("this:");
}
b.append(typeOfThis.toStringHelper(forAnnotations));
}
if (paramNum > 0) {
if (hasKnownTypeOfThis) {
b.append(", ");
}
Node p = call.parameters.getFirstChild();
appendArgString(b, p, forAnnotations);
p = p.getNext();
while (p != null) {
b.append(", ");
appendArgString(b, p, forAnnotations);
p = p.getNext();
}
}
b.append("): ");
b.append(call.returnType.toStringHelper(forAnnotations));
setPrettyPrint(true);
return b.toString();
}
private void appendArgString(
StringBuilder b, Node p, boolean forAnnotations) {
if (p.isVarArgs()) {
appendVarArgsString(b, p.getJSType(), forAnnotations);
} else if (p.isOptionalArg()) {
appendOptionalArgString(b, p.getJSType(), forAnnotations);
} else {
b.append(p.getJSType().toStringHelper(forAnnotations));
}
}
/** Gets the string representation of a var args param. */
private void appendVarArgsString(StringBuilder builder, JSType paramType,
boolean forAnnotations) {
if (paramType.isUnionType()) {
// Remove the optionality from the var arg.
paramType = paramType.toMaybeUnionType().getRestrictedUnion(
registry.getNativeType(JSTypeNative.VOID_TYPE));
}
builder.append("...[").append(
paramType.toStringHelper(forAnnotations)).append("]");
}
/** Gets the string representation of an optional param. */
private void appendOptionalArgString(
StringBuilder builder, JSType paramType, boolean forAnnotations) {
if (paramType.isUnionType()) {
// Remove the optionality from the var arg.
paramType = paramType.toMaybeUnionType().getRestrictedUnion(
registry.getNativeType(JSTypeNative.VOID_TYPE));
}
builder.append(paramType.toStringHelper(forAnnotations)).append("=");
}
/**
* A function is a subtype of another if their call methods are related via
* subtyping and {@code this} is a subtype of {@code that} with regard to
* the prototype chain.
*/
@Override
public boolean isSubtype(JSType that) {
if (JSType.isSubtypeHelper(this, that)) {
return true;
}
if (that.isFunctionType()) {
FunctionType other = that.toMaybeFunctionType();
if (other.isInterface()) {
// Any function can be assigned to an interface function.
return true;
}
if (isInterface()) {
// An interface function cannot be assigned to anything.
return false;
}
// If functionA is a subtype of functionB, then their "this" types
// should be contravariant. However, this causes problems because
// of the way we enforce overrides. Because function(this:SubFoo)
// is not a subtype of function(this:Foo), our override check treats
// this as an error. Let's punt on all this for now.
// TODO(nicksan
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>tos): fix this.
boolean treatThisTypesAsCovariant =
// An interface 'this'-type is non-restrictive.
// In practical terms, if C implements I, and I has a method m,
// then any m doesn't necessarily have to C#m's 'this'
// type doesn't need to match I.
(other.typeOfThis.toObjectType() != null &&
other.typeOfThis.toObjectType().getConstructor() != null &&
other.typeOfThis.toObjectType().getConstructor().isInterface()) ||
// If one of the 'this' types is covariant of the other,
// then we'll treat them as covariant (see comment above).
other.typeOfThis.isSubtype(this.typeOfThis) ||
this.typeOfThis.isSubtype(other.typeOfThis);
return treatThisTypesAsCovariant && this.call.isSubtype(other.call);
}
return getNativeType(JSTypeNative.FUNCTION_PROTOTYPE).isSubtype(that);
}
@Override
public <T> T visit(Visitor<T> visitor) {
return visitor.caseFunctionType(this);
}
@Override <T> T visit(RelationshipVisitor<T> visitor, JSType that) {
return visitor.caseFunctionType(this, that);
}
/**
* Gets the type of instance of this function.
* @throws IllegalStateException if this function is not a constructor
* (see {@link #isConstructor()}).
*/
public ObjectType getInstanceType() {
Preconditions.checkState(hasInstanceType());
return typeOfThis.toObjectType();
}
/**
* Sets the instance type. This should only be used for special
* native types.
*/
void setInstanceType(ObjectType instanceType) {
typeOfThis = instanceType;
}
/**
* Returns whether this function type has an instance type.
*/
public boolean hasInstanceType() {
return isConstructor() || isInterface();
}
/**
* Gets the type of {@code this} in this function.
*/
@Override
public JSType getTypeOfThis() {
return typeOfThis.isEmptyType() ?
registry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE) : typeOfThis;
}
/**
* Gets the source node or null if this is an unknown function.
*/
public Node getSource() {
return source;
}
/**
* Sets the source node.
*/
public void setSource(Node source) {
if (prototypeSlot != null) {
// NOTE(bashir): On one hand when source is null we want to drop any
// references to old nodes retained in prototypeSlot. On the other hand
// we cannot simply drop prototypeSlot, so we retain all information
// except the propertyNode for which we use an approximation! These
// details mostly matter in hot-swap passes.
if (source == null || prototypeSlot.getNode() == null) {
prototypeSlot = new Property(prototypeSlot.getName(),
prototypeSlot.getType(), prototypeSlot.isTypeInferred(), source);
}
}
this.source = source;
}
/** Adds a type to the list of subtypes for this type. */
private void addSubType(FunctionType subType) {
if (subTypes == null
Closure, 172
<FILEB>
<CHANGES>
String className = qName.substring(0, qName.lastIndexOf(".prototype"));
Var slot = scope.getSlot(className);
JSType classType = slot == null? null : slot.getType();
if (classType!= null
&& (classType.isConstructor() || classType.isInterface())) {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<FILEE>
<FILEB>
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
// Prototypes of constructors and interfaces are always declared.
if (qName != null && qName.endsWith(".prototype")) {
<CHANGES>
<CHANGEE>
return false;
<CHANGES>
<CHANGEE>
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (isConstantSymbol(info, n) && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
<FILEE>
<SCANS>) {
subTypes = Lists.newArrayList();
}
subTypes.add(subType);
}
@Override
public void clearCachedValues() {
super.clearCachedValues();
if (subTypes != null) {
for (FunctionType subType : subTypes) {
subType.clearCachedValues();
}
}
if (!isNativeObjectType()) {
if (hasInstanceType()) {
getInstanceType().clearCachedValues();
}
if (prototypeSlot != null) {
((ObjectType) prototypeSlot.getType()).clearCachedValues();
}
}
}
/**
* Returns a list of types that are subtypes of this type. This is only valid
* for constructor functions, and may be null. This allows a downward
* traversal of the subtype graph.
*/
public List<FunctionType> getSubTypes() {
return subTypes;
}
@Override
public boolean hasCachedValues() {
return prototypeSlot != null || super.hasCachedValues();
}
@Override
JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) {
setResolvedTypeInternal(this);
call = (ArrowType) safeResolve(call, t, scope);
if (prototypeSlot != null) {
prototypeSlot.setType(
safeResolve(prototypeSlot.getType(), t, scope));
}
// Warning about typeOfThis if it doesn't resolve to an ObjectType
// is handled further upstream.
//
// TODO(nicksantos): Handle this correctly if we have a UnionType.
//
// TODO(nicksantos): In ES3, the run-time coerces "null" to the global
// activation object. In ES5, it leaves it as null. Just punt on this
// issue for now by coercing out null. This is complicated by the
// fact that when most people write @this {Foo}, they really don't
// mean "nullable Foo". For certain tags (like @extends) we de-nullify
// the name for them.
JSType maybeTypeOfThis = safeResolve(typeOfThis, t, scope);
if (maybeTypeOfThis != null) {
maybeTypeOfThis = maybeTypeOfThis.restrictByNotNullOrUndefined();
}
if (maybeTypeOfThis instanceof ObjectType) {
typeOfThis = maybeTypeOfThis;
}
boolean changed = false;
ImmutableList.Builder<ObjectType> resolvedInterfaces =
ImmutableList.builder();
for (ObjectType iface : implementedInterfaces) {
ObjectType resolvedIface = (ObjectType) iface.resolve(t, scope);
resolvedInterfaces.add(resolvedIface);
changed |= (resolvedIface != iface);
}
if (changed) {
implementedInterfaces = resolvedInterfaces.build();
}
if (subTypes != null) {
for (int i = 0; i < subTypes.size(); i++) {
subTypes.set(
i, JSType.toMaybeFunctionType(subTypes.get(i).resolve(t, scope)));
}
}
return super.resolveInternal(t, scope);
}
@Override
public String toDebugHashCodeString() {
if (this == registry.getNativeType(JSTypeNative.FUNCTION_INSTANCE_TYPE)) {
return super.toDebugHashCodeString();
}
StringBuilder b = new StringBuilder(